From 56cbe2d8a2a5edfe97951a469c040022dc8527eb Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Sun, 27 Jun 2021 17:26:09 +0800 Subject: [PATCH] Redesign notice handling and introduce `NoticeProcessorPipeline` part 2 Do not broadcast StrangerAddedEvent if added twice, fix stranger scope not closed Do not add new instance if there is already one Close and remove corresponding stranger instance if there is new friend. --- .../commonMain/kotlin/event/events/friend.kt | 30 +-- .../kotlin/event/events/stranger.kt | 13 +- .../src/commonMain/kotlin/AbstractBot.kt | 5 +- mirai-core/src/commonMain/kotlin/MiraiImpl.kt | 4 + .../src/commonMain/kotlin/QQAndroidBot.kt | 6 - .../commonMain/kotlin/contact/AbstractUser.kt | 36 ++- .../commonMain/kotlin/contact/FriendImpl.kt | 15 +- .../commonMain/kotlin/contact/GroupImpl.kt | 3 +- .../kotlin/contact/NormalMemberImpl.kt | 25 +- .../commonMain/kotlin/contact/StrangerImpl.kt | 16 +- .../kotlin/contact/info/FriendInfoImpl.kt | 6 +- .../kotlin/contact/info/StrangerInfoImpl.kt | 6 +- .../kotlin/network/QQAndroidClient.kt | 12 +- .../network/components/ContactUpdater.kt | 16 +- .../components/NoticeProcessorPipeline.kt | 47 ++-- .../network/notice/BinaryMessageProcessor.kt | 68 +----- .../network/notice/FriendNoticeProcessor.kt | 206 ++++++++++++++++ .../notice/GroupEventProcessorContext.kt | 66 ------ .../notice/GroupListNoticeProcessor.kt | 140 ++++++----- .../network/notice/GroupMessageProcessor.kt | 23 +- .../network/notice/NewContactSupport.kt | 113 +++++++++ .../notice/OtherClientNoticeProcessor.kt | 139 +++++++++++ .../notice/PrivateMessageNoticeProcessor.kt | 94 ++++++++ .../network/notice/SystemMessageProcessor.kt | 221 +----------------- .../network/notice/decoders/MsgInfoDecoder.kt | 2 +- .../notice/decoders/MsgType0x210Decoder.kt | 2 +- .../protocol/data/jce/ReqPushStatus.kt | 4 +- .../protocol/data/proto/msgType0x210.kt | 2 +- .../network/protocol/packet/PacketFactory.kt | 14 ++ .../chat/receive/MessageSvc.PbSendMsg.kt | 4 +- .../receive/MessageSvc.RequestPushStatus.kt | 74 +----- .../packet/chat/receive/OnlinePush.ReqPush.kt | 170 ++------------ .../commonMain/kotlin/utils/AtomicIntSeq.kt | 78 +++++++ 33 files changed, 918 insertions(+), 742 deletions(-) create mode 100644 mirai-core/src/commonMain/kotlin/network/notice/FriendNoticeProcessor.kt delete mode 100644 mirai-core/src/commonMain/kotlin/network/notice/GroupEventProcessorContext.kt create mode 100644 mirai-core/src/commonMain/kotlin/network/notice/NewContactSupport.kt create mode 100644 mirai-core/src/commonMain/kotlin/network/notice/OtherClientNoticeProcessor.kt create mode 100644 mirai-core/src/commonMain/kotlin/network/notice/PrivateMessageNoticeProcessor.kt create mode 100644 mirai-core/src/commonMain/kotlin/utils/AtomicIntSeq.kt diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/friend.kt b/mirai-core-api/src/commonMain/kotlin/event/events/friend.kt index 5a38dd007..d28495c0c 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/friend.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/friend.kt @@ -28,34 +28,34 @@ import java.util.concurrent.atomic.AtomicBoolean /** * 好友昵称改变事件. 目前仅支持解析 (来自 PC 端的修改). */ -public data class FriendRemarkChangeEvent internal constructor( +public data class FriendRemarkChangeEvent @MiraiInternalApi public constructor( public override val friend: Friend, public val oldRemark: String, - public val newRemark: String + public val newRemark: String, ) : FriendEvent, Packet, AbstractEvent(), FriendInfoChangeEvent /** * 成功添加了一个新好友的事件 */ -public data class FriendAddEvent @MiraiInternalApi constructor( +public data class FriendAddEvent @MiraiInternalApi public constructor( /** * 新好友. 已经添加到 [Bot.friends] */ - public override val friend: Friend + public override val friend: Friend, ) : FriendEvent, Packet, AbstractEvent(), FriendInfoChangeEvent /** * 好友已被删除或主动删除的事件. */ -public data class FriendDeleteEvent internal constructor( - public override val friend: Friend +public data class FriendDeleteEvent @MiraiInternalApi public constructor( + public override val friend: Friend, ) : FriendEvent, Packet, AbstractEvent(), FriendInfoChangeEvent /** * 一个账号请求添加机器人为好友的事件 */ @Suppress("DEPRECATION") -public data class NewFriendRequestEvent internal constructor( +public data class NewFriendRequestEvent @MiraiInternalApi public constructor( public override val bot: Bot, /** * 事件唯一识别号 @@ -76,7 +76,7 @@ public data class NewFriendRequestEvent internal constructor( /** * 群名片或好友昵称 */ - public val fromNick: String + public val fromNick: String, ) : BotEvent, Packet, AbstractEvent(), FriendInfoChangeEvent { @JvmField internal val responded: AtomicBoolean = AtomicBoolean(false) @@ -97,25 +97,25 @@ public data class NewFriendRequestEvent internal constructor( /** * [Friend] 头像被修改. 在此事件广播前就已经修改完毕. */ -public data class FriendAvatarChangedEvent internal constructor( - public override val friend: Friend +public data class FriendAvatarChangedEvent @MiraiInternalApi public constructor( + public override val friend: Friend, ) : FriendEvent, Packet, AbstractEvent() /** * [Friend] 昵称改变事件, 在此事件广播时好友已经完成改名 * @see BotNickChangedEvent */ -public data class FriendNickChangedEvent internal constructor( +public data class FriendNickChangedEvent @MiraiInternalApi public constructor( public override val friend: Friend, public val from: String, - public val to: String + public val to: String, ) : FriendEvent, Packet, AbstractEvent(), FriendInfoChangeEvent /** * 好友输入状态改变的事件,当开始输入文字、退出聊天窗口或清空输入框时会触发此事件 */ -public data class FriendInputStatusChangedEvent internal constructor( +public data class FriendInputStatusChangedEvent @MiraiInternalApi public constructor( public override val friend: Friend, - public val inputting: Boolean + public val inputting: Boolean, -) : FriendEvent, Packet, AbstractEvent() \ No newline at end of file + ) : FriendEvent, Packet, AbstractEvent() \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/stranger.kt b/mirai-core-api/src/commonMain/kotlin/event/events/stranger.kt index 4d99b4336..d7e24b36d 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/stranger.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/stranger.kt @@ -24,7 +24,7 @@ public data class StrangerAddEvent @MiraiInternalApi public constructor( /** * 新的陌生人. 已经添加到 [Bot.strangers] */ - public override val stranger: Stranger + public override val stranger: Stranger, ) : StrangerEvent, Packet, AbstractEvent() @@ -33,19 +33,16 @@ public data class StrangerAddEvent @MiraiInternalApi public constructor( * */ public sealed class StrangerRelationChangeEvent( - public override val stranger: Stranger + public override val stranger: Stranger, ) : StrangerEvent, Packet, AbstractEvent() { /** - * 主动删除陌生人或陌生人被删除的事件 - * - * 除主动删除外,此事件为惰性广播,无法确保实时性 - * 目前被动删除仅会在陌生人二次添加时才会进行广播 + * 主动删除陌生人或陌生人被删除的事件, 不一定能接收到被动删除的事件 */ public class Deleted( /** * 被删除的陌生人 */ - stranger: Stranger + stranger: Stranger, ) : StrangerRelationChangeEvent(stranger) /** @@ -63,7 +60,7 @@ public sealed class StrangerRelationChangeEvent( * * 已经添加到Bot的好友列表中 */ - public val friend: Friend + public val friend: Friend, ) : StrangerRelationChangeEvent(stranger) } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/AbstractBot.kt b/mirai-core/src/commonMain/kotlin/AbstractBot.kt index d3a8246ab..59f504a43 100644 --- a/mirai-core/src/commonMain/kotlin/AbstractBot.kt +++ b/mirai-core/src/commonMain/kotlin/AbstractBot.kt @@ -95,10 +95,13 @@ internal abstract class AbstractBot constructor( final override val groups: ContactList<GroupImpl> = ContactList() final override val strangers: ContactList<StrangerImpl> = ContactList() - final override val asFriend: FriendImpl by lazy { Mirai.newFriend(this, FriendInfoImpl(uin, nick, "")).cast() } + final override val asFriend: FriendImpl by lazy { + Mirai.newFriend(this, FriendInfoImpl(uin, "", "")).cast() + } // nick is initialized later on login final override val asStranger: StrangerImpl by lazy { Mirai.newStranger(this, StrangerInfoImpl(bot.id, bot.nick)).cast() } + final override var nick: String by asFriend.info::nick override fun close(cause: Throwable?) { if (!this.isActive) return diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index 4406f7511..27b76e3b8 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -14,6 +14,8 @@ import io.ktor.client.engine.okhttp.* import io.ktor.client.features.* import io.ktor.client.request.* import io.ktor.client.request.forms.* +import io.ktor.http.* +import io.ktor.utils.io.core.* import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.currentCoroutineContext import kotlinx.io.core.discardExact @@ -30,7 +32,9 @@ import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.contact.* import net.mamoe.mirai.internal.contact.info.FriendInfoImpl +import net.mamoe.mirai.internal.contact.info.FriendInfoImpl.Companion.impl import net.mamoe.mirai.internal.contact.info.MemberInfoImpl +import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl.Companion.impl import net.mamoe.mirai.internal.message.* import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.network.components.EventDispatcher diff --git a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt index 5006134c1..30a5919c9 100644 --- a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt +++ b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt @@ -230,12 +230,6 @@ internal open class QQAndroidBot constructor( get() = client.wLoginSigInfo.sKey.data .fold(5381) { acc: Int, b: Byte -> acc + acc.shl(5) + b.toInt() } .and(Int.MAX_VALUE) - - /////////////////////////////////////////////////////////////////////////// - // contacts - /////////////////////////////////////////////////////////////////////////// - - override lateinit var nick: String } internal fun QQAndroidBot.getGroupByUinOrFail(uin: Long) = diff --git a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt index 41874e0fe..c731dadfd 100644 --- a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt +++ b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt @@ -10,6 +10,9 @@ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.Mirai +import net.mamoe.mirai.contact.Friend +import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.contact.User import net.mamoe.mirai.data.UserInfo import net.mamoe.mirai.event.broadcast @@ -28,17 +31,32 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352 import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect +import net.mamoe.mirai.internal.utils.AtomicIntSeq +import net.mamoe.mirai.internal.utils.C2CPkgMsgParsingCache import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.message.MessageReceipt -import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.message.data.Message -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.isContentEmpty +import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.* +import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext internal val User.info: UserInfo? get() = this.castOrNull<AbstractUser>()?.info +@Suppress("NOTHING_TO_INLINE") +internal inline fun User.impl(): AbstractUser { + contract { returns() implies (this@impl is AbstractUser) } + check(this is AbstractUser) + return this +} + +internal val User.correspondingMessageSourceKind + get() = when (this) { + is Friend -> MessageSourceKind.FRIEND + is Member -> MessageSourceKind.TEMP + is Stranger -> MessageSourceKind.STRANGER + else -> error("Unknown user: ${this::class.qualifiedName}") + } + internal abstract class AbstractUser( bot: QQAndroidBot, parentCoroutineContext: CoroutineContext, @@ -49,6 +67,9 @@ internal abstract class AbstractUser( final override var nick: String = userInfo.nick final override val remark: String = userInfo.remark + val messageSeq = AtomicIntSeq.forMessageSeq() + val fragmentedMessageMerger = C2CPkgMsgParsingCache() + open val info: UserInfo = userInfo @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @@ -58,7 +79,8 @@ internal abstract class AbstractUser( } val resp = bot.network.run { LongConn.OffPicUp( - bot.client, Cmd0x352.TryUpImgReq( + bot.client, + Cmd0x352.TryUpImgReq( buType = 1, srcUin = bot.id, dstUin = this@AbstractUser.id, @@ -66,8 +88,8 @@ internal abstract class AbstractUser( fileSize = resource.size, fileName = resource.md5.toUHexString("") + "." + resource.formatName, imgOriginal = true, - buildVer = bot.client.buildVer - ) + buildVer = bot.client.buildVer, + ), ).sendAndExpect<LongConn.OffPicUp.Response>() } diff --git a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt index 35cf93d25..d06fe3d29 100644 --- a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt @@ -14,12 +14,9 @@ package net.mamoe.mirai.internal.contact -import kotlinx.atomicfu.AtomicInt -import kotlinx.atomicfu.atomic import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Friend -import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.event.events.FriendMessagePostSendEvent import net.mamoe.mirai.event.events.FriendMessagePreSendEvent import net.mamoe.mirai.internal.QQAndroidBot @@ -31,7 +28,6 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.audioCodec import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList -import net.mamoe.mirai.internal.utils.C2CPkgMsgParsingCache import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.MessageReceipt @@ -53,9 +49,9 @@ internal fun net.mamoe.mirai.internal.network.protocol.data.jce.FriendInfo.toMir ) @OptIn(ExperimentalContracts::class) -internal inline fun Friend.checkIsFriendImpl(): FriendImpl { +internal inline fun Friend.impl(): FriendImpl { contract { - returns() implies (this@checkIsFriendImpl is FriendImpl) + returns() implies (this@impl is FriendImpl) } check(this is FriendImpl) { "A Friend instance is not instance of FriendImpl. Your instance: ${this::class.qualifiedName}" } return this @@ -64,11 +60,8 @@ internal inline fun Friend.checkIsFriendImpl(): FriendImpl { internal class FriendImpl( bot: QQAndroidBot, parentCoroutineContext: CoroutineContext, - internal val friendInfo: FriendInfo, -) : Friend, AbstractUser(bot, parentCoroutineContext, friendInfo) { - @Suppress("unused") // bug - val lastMessageSequence: AtomicInt = atomic(-1) - val friendPkgMsgParsingCache = C2CPkgMsgParsingCache() + override val info: FriendInfoImpl, +) : Friend, AbstractUser(bot, parentCoroutineContext, info) { override suspend fun delete() { check(bot.friends[this.id] != null) { "Friend ${this.id} had already been deleted" diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index a297aadf6..c44bf909f 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -303,7 +303,8 @@ internal fun Group.newMember(memberInfo: MemberInfo): Member { ) } -internal fun Group.addNewNormalMember(memberInfo: MemberInfo): NormalMemberImpl { +internal fun Group.addNewNormalMember(memberInfo: MemberInfo): NormalMemberImpl? { + if (members.contains(memberInfo.uin)) return null return newNormalMember(memberInfo).also { members.delegate.add(it) } diff --git a/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt b/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt index bfab6397f..1eedb8e25 100644 --- a/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt @@ -11,8 +11,6 @@ package net.mamoe.mirai.internal.contact -import kotlinx.atomicfu.AtomicInt -import kotlinx.atomicfu.atomic import kotlinx.coroutines.CancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.launch @@ -36,12 +34,9 @@ import kotlin.coroutines.CoroutineContext @Suppress("MemberVisibilityCanBePrivate") internal class NormalMemberImpl constructor( group: GroupImpl, - coroutineContext: CoroutineContext, + parentCoroutineContext: CoroutineContext, memberInfo: MemberInfo, -) : NormalMember, AbstractMember(group, coroutineContext, memberInfo) { - - @Suppress("unused") // false positive - val lastMessageSequence: AtomicInt = atomic(-1) +) : NormalMember, AbstractMember(group, parentCoroutineContext, memberInfo) { override val joinTimestamp: Int get() = info.joinTimestamp override val lastSpeakTimestamp: Int get() = info.lastSpeakTimestamp @@ -57,7 +52,7 @@ internal class NormalMemberImpl constructor( ?: handler.sendMessageImpl<NormalMember>( message = message, preSendEventConstructor = ::GroupTempMessagePreSendEvent, - postSendEventConstructor = ::GroupTempMessagePostSendEvent.cast() + postSendEventConstructor = ::GroupTempMessagePostSendEvent.cast(), ) } @@ -102,7 +97,7 @@ internal class NormalMemberImpl constructor( TroopManagement.EditGroupNametag( bot.client, this@NormalMemberImpl, - newValue + newValue, ).sendWithoutExpect() } MemberCardChangeEvent(oldValue, newValue, this@NormalMemberImpl).broadcast() @@ -122,7 +117,7 @@ internal class NormalMemberImpl constructor( TroopManagement.EditSpecialTitle( bot.client, this@NormalMemberImpl, - newValue + newValue, ).sendWithoutExpect() } MemberSpecialTitleChangeEvent(oldValue, newValue, this@NormalMemberImpl, null).broadcast() @@ -143,7 +138,7 @@ internal class NormalMemberImpl constructor( client = bot.client, groupCode = group.id, memberUin = this@NormalMemberImpl.id, - timeInSecond = durationSeconds + timeInSecond = durationSeconds, ).sendAndExpect<TroopManagement.Mute.Response>() } @@ -159,7 +154,7 @@ internal class NormalMemberImpl constructor( client = bot.client, groupCode = group.id, memberUin = this@NormalMemberImpl.id, - timeInSecond = 0 + timeInSecond = 0, ).sendAndExpect<TroopManagement.Mute.Response>() } @@ -210,7 +205,7 @@ internal class NormalMemberImpl constructor( val resp: TroopManagement.ModifyAdmin.Response = TroopManagement.ModifyAdmin( client = bot.client, member = this@NormalMemberImpl, - operation = operation + operation = operation, ).sendAndExpect() check(resp.success) { @@ -227,7 +222,7 @@ internal class NormalMemberImpl constructor( internal fun Member.checkBotPermissionHighest(operationName: String) { check(group.botPermission == MemberPermission.OWNER) { throw PermissionDeniedException( - "`$operationName` operation requires the OWNER permission, while bot has ${group.botPermission}" + "`$operationName` operation requires the OWNER permission, while bot has ${group.botPermission}", ) } } @@ -236,7 +231,7 @@ internal fun Member.checkBotPermissionHigherThanThis(operationName: String) { check(group.botPermission > this.permission) { throw PermissionDeniedException( "`$operationName` operation requires a higher permission, while " + - "${group.botPermission} < ${this.permission}" + "${group.botPermission} < ${this.permission}", ) } } diff --git a/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt b/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt index 2b900bf58..e43fcd2e2 100644 --- a/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt @@ -17,8 +17,6 @@ package net.mamoe.mirai.internal.contact -import kotlinx.atomicfu.AtomicInt -import kotlinx.atomicfu.atomic import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.contact.User @@ -39,10 +37,8 @@ import kotlin.coroutines.CoroutineContext @OptIn(ExperimentalContracts::class) -internal inline fun Stranger.checkIsImpl(): StrangerImpl { - contract { - returns() implies (this@checkIsImpl is StrangerImpl) - } +internal inline fun Stranger.impl(): StrangerImpl { + contract { returns() implies (this@impl is StrangerImpl) } check(this is StrangerImpl) { "A Stranger instance is not instance of StrangerImpl. Your instance: ${this::class.qualifiedName}" } return this } @@ -50,10 +46,8 @@ internal inline fun Stranger.checkIsImpl(): StrangerImpl { internal class StrangerImpl( bot: QQAndroidBot, parentCoroutineContext: CoroutineContext, - internal val strangerInfo: StrangerInfo, -) : Stranger, AbstractUser(bot, parentCoroutineContext, strangerInfo) { - @Suppress("unused") // bug - val lastMessageSequence: AtomicInt = atomic(-1) + override val info: StrangerInfo, +) : Stranger, AbstractUser(bot, parentCoroutineContext, info) { override suspend fun delete() { check(bot.strangers[this.id] != null) { "Stranger ${this.id} had already been deleted" @@ -66,7 +60,7 @@ internal class StrangerImpl( } } - private val handler by lazy { StrangerSendMessageHandler(this) } + private val handler: StrangerSendMessageHandler by lazy { StrangerSendMessageHandler(this) } @Suppress("DuplicatedCode") override suspend fun sendMessage(message: Message): MessageReceipt<Stranger> { diff --git a/mirai-core/src/commonMain/kotlin/contact/info/FriendInfoImpl.kt b/mirai-core/src/commonMain/kotlin/contact/info/FriendInfoImpl.kt index c46c8b135..bb1702798 100644 --- a/mirai-core/src/commonMain/kotlin/contact/info/FriendInfoImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/info/FriendInfoImpl.kt @@ -18,4 +18,8 @@ internal data class FriendInfoImpl( override val uin: Long, override var nick: String, override var remark: String, -) : FriendInfo \ No newline at end of file +) : FriendInfo { + companion object { + fun FriendInfo.impl() = if (this is FriendInfoImpl) this else FriendInfoImpl(uin, nick, remark) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/contact/info/StrangerInfoImpl.kt b/mirai-core/src/commonMain/kotlin/contact/info/StrangerInfoImpl.kt index 6d55d00e1..6ec1a4604 100644 --- a/mirai-core/src/commonMain/kotlin/contact/info/StrangerInfoImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/info/StrangerInfoImpl.kt @@ -20,4 +20,8 @@ internal class StrangerInfoImpl( override val nick: String, override val fromGroup: Long = 0, override val remark: String = "", -) : StrangerInfo \ No newline at end of file +) : StrangerInfo { + companion object { + fun StrangerInfo.impl() = if (this is StrangerInfoImpl) this else StrangerInfoImpl(uin, nick, fromGroup, remark) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt index 574eea322..d79778e9b 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt @@ -24,6 +24,7 @@ import net.mamoe.mirai.internal.network.components.SsoSession import net.mamoe.mirai.internal.network.protocol.SyncingCacheList import net.mamoe.mirai.internal.network.protocol.data.jce.FileStoragePushFSSvcList import net.mamoe.mirai.internal.network.protocol.packet.Tlv +import net.mamoe.mirai.internal.utils.AtomicIntSeq import net.mamoe.mirai.internal.utils.MiraiProtocolInternal import net.mamoe.mirai.internal.utils.NetworkType import net.mamoe.mirai.utils.* @@ -124,13 +125,10 @@ internal open class QQAndroidClient( internal var strangerSeq: Int = 0 - // TODO: 2021/4/14 investigate whether they can be minimized - private val friendSeq: AtomicInt = atomic(getRandomUnsignedInt()) - internal fun getFriendSeq(): Int = friendSeq.value - - internal fun nextFriendSeq(): Int = friendSeq.incrementAndGet() - - internal fun setFriendSeq(compare: Int, id: Int): Boolean = friendSeq.compareAndSet(compare, id % 65535) + /** + * for send + */ + val sendFriendMessageSeq = AtomicIntSeq.forPrivateSync() internal val groupConfig: GroupConfig = GroupConfig() diff --git a/mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt b/mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt index 093d90f29..0efb02ace 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt @@ -19,10 +19,8 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withPermit import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.internal.QQAndroidBot -import net.mamoe.mirai.internal.contact.FriendImpl import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.contact.StrangerImpl import net.mamoe.mirai.internal.contact.info.FriendInfoImpl @@ -34,6 +32,7 @@ import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.isValid +import net.mamoe.mirai.internal.network.notice.NewContactSupport import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopNum import net.mamoe.mirai.internal.network.protocol.data.jce.SvcRespRegister import net.mamoe.mirai.internal.network.protocol.data.jce.isValid @@ -76,7 +75,7 @@ internal class ContactUpdaterImpl( val bot: QQAndroidBot, // not good val components: ComponentStorage, private val logger: MiraiLogger, -) : ContactUpdater { +) : ContactUpdater, NewContactSupport { override val otherClientsLock: Mutex = Mutex() override val groupListModifyLock: Mutex = Mutex() private val cacheService get() = components[ContactCacheService] @@ -176,16 +175,13 @@ internal class ContactUpdaterImpl( } for (friendInfoImpl in list) { - addFriendToBot(friendInfoImpl) + bot.addNewFriendAndRemoveStranger(friendInfoImpl) } initFriendOk = true } - private fun addFriendToBot(it: FriendInfo) = - bot.friends.delegate.add(FriendImpl(bot, bot.coroutineContext, it)) - private suspend fun addGroupToBot(stTroopNum: StTroopNum) = stTroopNum.run { suspend fun refreshGroupMemberList(): Sequence<MemberInfo> { return Mirai.getRawGroupMemberList( @@ -214,11 +210,11 @@ internal class ContactUpdaterImpl( bot.groups.delegate.add( GroupImpl( bot = bot, - coroutineContext = bot.coroutineContext, + parentCoroutineContext = bot.coroutineContext, id = groupCode, groupInfo = GroupInfoImpl(stTroopNum), - members = members - ) + members = members, + ), ) } diff --git a/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt b/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt index 0cee4b7ee..00ef4e189 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt @@ -17,6 +17,7 @@ import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.notice.decoders.MsgType0x2DC import net.mamoe.mirai.internal.network.protocol.data.jce.MsgInfo import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 +import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushStatus import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMsgInfo @@ -32,6 +33,19 @@ import kotlin.concurrent.read import kotlin.concurrent.write import kotlin.reflect.KClass +/** + * Centralized processor pipeline for [MessageSvcPbGetMsg] and [OnlinePushPbPushTransMsg] + */ +internal interface NoticeProcessorPipeline { + fun registerProcessor(processor: NoticeProcessor) + + suspend fun process(bot: QQAndroidBot, data: Any?, attributes: TypeSafeMap = TypeSafeMap()): Collection<Packet> + + companion object : ComponentKey<NoticeProcessorPipeline> { + val ComponentStorage.noticeProcessorPipeline get() = get(NoticeProcessorPipeline) + } +} + internal interface PipelineContext { val bot: QQAndroidBot val attributes: TypeSafeMap @@ -40,7 +54,7 @@ internal interface PipelineContext { val isConsumed: Boolean /** - * Mark the input as consumed so that there will not be warnings like 'Unknown type xxx' + * Mark the input as consumed so that there will not be warnings like 'Unknown type xxx'. This will not stop the pipeline. * * If this is executed, make sure you provided all information important for debugging. * @@ -79,19 +93,6 @@ internal interface PipelineContext { internal inline val PipelineContext.context get() = this -/** - * Centralized processor pipeline for [MessageSvcPbGetMsg] and [OnlinePushPbPushTransMsg] - */ -internal interface NoticeProcessorPipeline { - fun registerProcessor(processor: NoticeProcessor) - - suspend fun process(bot: QQAndroidBot, data: Any?, attributes: TypeSafeMap = TypeSafeMap()): Collection<Packet> - - companion object : ComponentKey<NoticeProcessorPipeline> { - val ComponentStorage.noticeProcessorPipeline get() = get(NoticeProcessorPipeline) - } -} - internal class NoticeProcessorPipelineImpl( private val logger: MiraiLogger, ) : NoticeProcessorPipeline { @@ -142,6 +143,10 @@ internal class NoticeProcessorPipelineImpl( } +/////////////////////////////////////////////////////////////////////////// +// NoticeProcessor +/////////////////////////////////////////////////////////////////////////// + /** * A processor handling some specific type of message. */ @@ -157,11 +162,11 @@ internal abstract class SimpleNoticeProcessor<T : Any>( final override suspend fun process(context: PipelineContext, data: Any?) { if (type.isInstance(data)) { - context.process0(data.uncheckedCast()) + context.processImpl(data.uncheckedCast()) } } - protected abstract suspend fun PipelineContext.process0(data: T) + protected abstract suspend fun PipelineContext.processImpl(data: T) companion object { @JvmStatic @@ -170,11 +175,11 @@ internal abstract class SimpleNoticeProcessor<T : Any>( } internal abstract class MsgCommonMsgProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type()) { - abstract override suspend fun PipelineContext.process0(data: MsgComm.Msg) + abstract override suspend fun PipelineContext.processImpl(data: MsgComm.Msg) } internal abstract class MixedNoticeProcessor : AnyNoticeProcessor() { - final override suspend fun PipelineContext.process0(data: Any) { + final override suspend fun PipelineContext.processImpl(data: Any) { when (data) { is MsgInfo -> processImpl(data) is PbMsgInfo -> processImpl(data) @@ -183,14 +188,16 @@ internal abstract class MixedNoticeProcessor : AnyNoticeProcessor() { is MsgType0x210 -> processImpl(data) is MsgType0x2DC -> processImpl(data) is Structmsg.StructMsg -> processImpl(data) + is RequestPushStatus -> processImpl(data) } } protected open suspend fun PipelineContext.processImpl(data: MsgInfo) {} - protected open suspend fun PipelineContext.processImpl(data: MsgType0x210) {} - protected open suspend fun PipelineContext.processImpl(data: MsgType0x2DC) {} + protected open suspend fun PipelineContext.processImpl(data: MsgType0x210) {} // 528 + protected open suspend fun PipelineContext.processImpl(data: MsgType0x2DC) {} // 732 protected open suspend fun PipelineContext.processImpl(data: PbMsgInfo) {} protected open suspend fun PipelineContext.processImpl(data: MsgOnlinePush.PbPushMsg) {} protected open suspend fun PipelineContext.processImpl(data: MsgComm.Msg) {} protected open suspend fun PipelineContext.processImpl(data: Structmsg.StructMsg) {} + protected open suspend fun PipelineContext.processImpl(data: RequestPushStatus) {} } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/notice/BinaryMessageProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/BinaryMessageProcessor.kt index 4d80ec9a2..e056e2b18 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/BinaryMessageProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/BinaryMessageProcessor.kt @@ -9,8 +9,6 @@ package net.mamoe.mirai.internal.network.notice -import kotlinx.io.core.discardExact -import kotlinx.io.core.readUByte import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.network.components.PipelineContext import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor @@ -18,27 +16,12 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMs import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.utils.read -internal class BinaryMessageProcessor : SimpleNoticeProcessor<PbMsgInfo>(type()), GroupEventProcessorContext { - override suspend fun PipelineContext.process0(data: PbMsgInfo) { +internal class BinaryMessageProcessor : SimpleNoticeProcessor<PbMsgInfo>(type()), NewContactSupport { + override suspend fun PipelineContext.processImpl(data: PbMsgInfo) { data.msgData.read<Unit> { when (data.msgType) { 44 -> { - // 3D C4 33 DD 01 FF CD 76 F4 03 C3 7E 2E 34 - // 群转让 - // start with 3D C4 33 DD 01 FF - // 3D C4 33 DD 01 FF C3 7E 2E 34 CD 76 F4 03 - // 权限变更 - // 3D C4 33 DD 01 00/01 ..... - // 3D C4 33 DD 01 01 C3 7E 2E 34 01 - this.discardExact(5) - when (val mode = readUByte().toInt()) { - 0xFF -> { - TODO("removed") - } - else -> { - TODO("removed") - } - } + TODO("removed") } 34 -> { TODO("removed") @@ -46,50 +29,7 @@ internal class BinaryMessageProcessor : SimpleNoticeProcessor<PbMsgInfo>(type()) else -> { when { data.msgType == 529 && data.msgSubtype == 9 -> { - /* - PbMsgInfo#1773430973 { -fromUin=0x0000000026BA1173(649728371) -generalFlag=0x00000001(1) -msgData=0A 07 70 72 69 6E 74 65 72 10 02 1A CD 02 0A 1F 53 61 6D 73 75 6E 67 20 4D 4C 2D 31 38 36 30 20 53 65 72 69 65 73 20 28 55 53 42 30 30 31 29 0A 16 4F 6E 65 4E 6F 74 65 20 66 6F 72 20 57 69 6E 64 6F 77 73 20 31 30 0A 19 50 68 61 6E 74 6F 6D 20 50 72 69 6E 74 20 74 6F 20 45 76 65 72 6E 6F 74 65 0A 11 4F 6E 65 4E 6F 74 65 20 28 44 65 73 6B 74 6F 70 29 0A 1D 4D 69 63 72 6F 73 6F 66 74 20 58 50 53 20 44 6F 63 75 6D 65 6E 74 20 57 72 69 74 65 72 0A 16 4D 69 63 72 6F 73 6F 66 74 20 50 72 69 6E 74 20 74 6F 20 50 44 46 0A 15 46 6F 78 69 74 20 50 68 61 6E 74 6F 6D 20 50 72 69 6E 74 65 72 0A 03 46 61 78 32 09 0A 03 6A 70 67 10 01 18 00 32 0A 0A 04 6A 70 65 67 10 01 18 00 32 09 0A 03 70 6E 67 10 01 18 00 32 09 0A 03 67 69 66 10 01 18 00 32 09 0A 03 62 6D 70 10 01 18 00 32 09 0A 03 64 6F 63 10 01 18 01 32 0A 0A 04 64 6F 63 78 10 01 18 01 32 09 0A 03 74 78 74 10 00 18 00 32 09 0A 03 70 64 66 10 01 18 01 32 09 0A 03 70 70 74 10 01 18 01 32 0A 0A 04 70 70 74 78 10 01 18 01 32 09 0A 03 78 6C 73 10 01 18 01 32 0A 0A 04 78 6C 73 78 10 01 18 01 -msgSeq=0x00001AFF(6911) -msgSubtype=0x00000009(9) -msgTime=0x5FDF21A3(1608458659) -msgType=0x00000211(529) -msgUid=0x010000005FDEE04C(72057595646369868) -realMsgTime=0x5FDF21A3(1608458659) -svrIp=0x3E689409(1047041033) -toUin=0x0000000026BA1173(649728371) -} - */ - /* - * -printer -Samsung ML-1860 Series (USB001) -OneNote for Windows 10 -Phantom Print to Evernote -OneNote (Desktop) -Microsoft XPS Document Writer -Microsoft Print to PDF -Foxit Phantom Printer -Fax2 -jpg2 - -jpeg2 -png2 -gif2 -bmp2 -doc2 - -docx2 -txt2 -pdf2 -ppt2 - -pptx2 -xls2 - -xlsx*/ - return + TODO("removed") } } throw contextualBugReportException( diff --git a/mirai-core/src/commonMain/kotlin/network/notice/FriendNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/FriendNoticeProcessor.kt new file mode 100644 index 000000000..4c2fbc65a --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/notice/FriendNoticeProcessor.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2019-2021 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/master/LICENSE + */ + +package net.mamoe.mirai.internal.network.notice + +import kotlinx.io.core.discardExact +import kotlinx.io.core.readUByte +import kotlinx.io.core.readUShort +import net.mamoe.mirai.event.events.* +import net.mamoe.mirai.internal.contact.impl +import net.mamoe.mirai.internal.contact.info.FriendInfoImpl +import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl +import net.mamoe.mirai.internal.contact.toMiraiFriendInfo +import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor +import net.mamoe.mirai.internal.network.components.PipelineContext +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.Submsgtype0x115.SubMsgType0x115 +import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27.SubMsgType0x27.* +import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x44.Submsgtype0x44 +import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0xb3.SubMsgType0xb3 +import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList.GetFriendGroupList +import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect +import net.mamoe.mirai.internal.utils._miraiContentToString +import net.mamoe.mirai.internal.utils.io.serialization.loadAs +import net.mamoe.mirai.utils.* + +/** + * All [FriendEvent] except [FriendMessageEvent] + * + * @see FriendInputStatusChangedEvent + * @see FriendAddEvent + * @see StrangerRelationChangeEvent.Friended + */ +internal class FriendNoticeProcessor( + private val logger: MiraiLogger, +) : MixedNoticeProcessor(), NewContactSupport { + override suspend fun PipelineContext.processImpl(data: MsgComm.Msg) = data.context { + if (msgHead.msgType != 191) return + + var fromGroup = 0L + var pbNick = "" + msgBody.msgContent.read { + readUByte() // version + discardExact(readUByte().toInt()) //skip + readUShort() //source id + readUShort() //SourceSubID + discardExact(readUShort().toLong()) //skip size + if (readUShort().toInt() != 0) { //hasExtraInfo + discardExact(readUShort().toInt()) //mail address info, skip + } + discardExact(4 + readUShort().toInt()) //skip + for (i in 1..readUByte().toInt()) { //pb size + val type = readUShort().toInt() + val pbArray = ByteArray(readUShort().toInt() and 0xFF) + readAvailable(pbArray) + when (type) { + 1000 -> pbArray.loadAs(FrdSysMsg.GroupInfo.serializer()).let { fromGroup = it.groupUin } + 1002 -> pbArray.loadAs(FrdSysMsg.FriendMiscInfo.serializer()) + .let { pbNick = it.fromuinNick } + else -> { + } //ignore + } + } + } + + msgHead.context { + if (fromUin == authUin) { + logger.error { "Could not determine uin since `fromUin` = `authUin` = $fromUin" } + return + } + val id = fromUin or authUin // 对方 qq + if (bot.getStranger(id) != null) return + + val nick = fromNick.ifEmpty { authNick }.ifEmpty { pbNick } + collect(StrangerAddEvent(bot.addNewStranger(StrangerInfoImpl(id, nick, fromGroup)) ?: return)) + } + + } + + override suspend fun PipelineContext.processImpl(data: MsgType0x210) = data.context { + when (data.uSubMsgType) { + 0xB3L -> { + // 08 01 12 52 08 A2 FF 8C F0 03 10 00 1D 15 3D 90 5E 22 2E E6 88 91 E4 BB AC E5 B7 B2 E7 BB 8F E6 98 AF E5 A5 BD E5 8F 8B E5 95 A6 EF BC 8C E4 B8 80 E8 B5 B7 E6 9D A5 E8 81 8A E5 A4 A9 E5 90 A7 21 2A 09 48 69 6D 31 38 38 6D 6F 65 30 07 38 03 48 DD F1 92 B7 07 + val body: SubMsgType0xb3.MsgBody = vProtobuf.loadAs(SubMsgType0xb3.MsgBody.serializer()) + handleFriendAddedB(data, body) + } + 0x44L -> { + val body = vProtobuf.loadAs(Submsgtype0x44.MsgBody.serializer()) + handleFriendAddedA(body) + } + 0x27L -> { + val body = vProtobuf.loadAs(SubMsgType0x27MsgBody.serializer()) + for (msgModInfo in body.msgModInfos) { + when { + msgModInfo.msgModFriendRemark != null -> handleRemarkChanged(msgModInfo.msgModFriendRemark) + msgModInfo.msgDelFriend != null -> handleFriendDeleted(msgModInfo.msgDelFriend) + msgModInfo.msgModCustomFace != null -> handleAvatarChanged(msgModInfo.msgModCustomFace) + msgModInfo.msgModProfile != null -> handleProfileChanged(msgModInfo.msgModProfile) + } + } + } + 0x115L -> { + val body = vProtobuf.loadAs(SubMsgType0x115.MsgBody.serializer()) + handleInputStatusChanged(body) + } + else -> return + } + markAsConsumed() + } + + private fun PipelineContext.handleInputStatusChanged(body: SubMsgType0x115.MsgBody) { + val friend = bot.getFriend(body.fromUin) ?: return + val item = body.msgNotifyItem ?: return + collect(FriendInputStatusChangedEvent(friend, item.eventType == 1)) + } + + private fun PipelineContext.handleProfileChanged(body: ModProfile) { + var containsUnknown = false + for (profileInfo in body.msgProfileInfos) { + when (profileInfo.field) { + 20002 -> { // 昵称修改 + val to = profileInfo.value + if (body.uin == bot.id) { + val from = bot.nick + if (from == to) continue + collect(BotNickChangedEvent(bot, from, to)) + bot.nick = to + } else { + val friend = bot.getFriend(body.uin)?.impl() ?: continue + val from = bot.nick + if (from == to) continue + collect(FriendNickChangedEvent(friend, from, to)) + friend.info.nick = to + } + } + else -> containsUnknown = true + } + } + if (body.msgProfileInfos.isEmpty() || containsUnknown) { + logger.debug { "Transformers528 0x27L: ProfileChanged new data: ${body._miraiContentToString()}" } + } + } + + private fun PipelineContext.handleRemarkChanged(body: ModFriendRemark) { + for (new in body.msgFrdRmk) { + val friend = bot.getFriend(new.fuin)?.impl() ?: continue + + // TODO: 2020/4/10 ADD REMARK QUERY + collect(FriendRemarkChangeEvent(friend, friend.remark, new.rmkName)) + friend.info.remark = new.rmkName + } + } + + private fun PipelineContext.handleAvatarChanged(body: ModCustomFace) { + if (body.uin == bot.id) { + collect(BotAvatarChangedEvent(bot)) + } + collect(FriendAvatarChangedEvent(bot.getFriend(body.uin) ?: return)) + } + + private fun PipelineContext.handleFriendDeleted(body: DelFriend) { + for (id in body.uint64Uins) { + collect(FriendDeleteEvent(bot.removeFriend(id) ?: continue)) + } + } + + private suspend fun PipelineContext.handleFriendAddedA( + body: Submsgtype0x44.MsgBody, + ) = body.msgFriendMsgSync.context { + if (this == null) return + + when (processtype) { + 3, 9, 10 -> { + if (bot.getFriend(fuin) != null) return + + val response = GetFriendGroupList.forSingleFriend(bot.client, fuin).sendAndExpect(bot) + val info = response.friendList.firstOrNull() ?: return + collect( + FriendAddEvent(bot.addNewFriendAndRemoveStranger(info.toMiraiFriendInfo()) ?: return), + ) + + } + } + } + + private fun PipelineContext.handleFriendAddedB(data: MsgType0x210, body: SubMsgType0xb3.MsgBody) = data.context { + val info = FriendInfoImpl( + uin = body.msgAddFrdNotify.fuin, + nick = body.msgAddFrdNotify.fuinNick, + remark = "", + ) + + val removed = bot.removeStranger(info.uin) + val added = bot.addNewFriendAndRemoveStranger(info) ?: return + collect(FriendAddEvent(added)) + if (removed != null) collect(StrangerRelationChangeEvent.Friended(removed, added)) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/notice/GroupEventProcessorContext.kt b/mirai-core/src/commonMain/kotlin/network/notice/GroupEventProcessorContext.kt deleted file mode 100644 index f6d7a75df..000000000 --- a/mirai-core/src/commonMain/kotlin/network/notice/GroupEventProcessorContext.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2019-2021 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/master/LICENSE - */ - -package net.mamoe.mirai.internal.network.notice - -import net.mamoe.mirai.Mirai -import net.mamoe.mirai.contact.MemberPermission -import net.mamoe.mirai.internal.QQAndroidBot -import net.mamoe.mirai.internal.contact.GroupImpl -import net.mamoe.mirai.internal.contact.info.GroupInfoImpl -import net.mamoe.mirai.internal.contact.info.MemberInfoImpl -import net.mamoe.mirai.internal.getGroupByUin -import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm -import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList -import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect - -internal interface GroupEventProcessorContext { - - fun MsgComm.Msg.getNewMemberInfo(): MemberInfoImpl { - return MemberInfoImpl( - nameCard = msgHead.authNick.ifEmpty { msgHead.fromNick }, - permission = MemberPermission.MEMBER, - specialTitle = "", - muteTimestamp = 0, - uin = msgHead.authUin, - nick = msgHead.authNick.ifEmpty { msgHead.fromNick }, - remark = "", - anonymousId = null - ) - } - - suspend fun QQAndroidBot.createGroupForBot(groupUin: Long): GroupImpl? { - val group = getGroupByUin(groupUin) - if (group != null) { - return null - } - - return getNewGroup(Mirai.calculateGroupCodeByGroupUin(groupUin))?.apply { groups.delegate.add(this) } - } - - suspend fun QQAndroidBot.getNewGroup(groupCode: Long): GroupImpl? { - val troopNum = FriendList.GetTroopListSimplify(client) - .sendAndExpect(network, timeoutMillis = 10_000, retry = 5) - .groups.firstOrNull { it.groupCode == groupCode } ?: return null - - return GroupImpl( - bot = this, - coroutineContext = coroutineContext, - id = groupCode, - groupInfo = GroupInfoImpl(troopNum), - members = Mirai.getRawGroupMemberList( - this, - troopNum.groupUin, - troopNum.groupCode, - troopNum.dwGroupOwnerUin - ) - ) - } - -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/notice/GroupListNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/GroupListNoticeProcessor.kt index 034594158..0df4ca1e5 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/GroupListNoticeProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/GroupListNoticeProcessor.kt @@ -39,6 +39,9 @@ import net.mamoe.mirai.utils.read /** + * Member/Bot invited/active join // force/active leave + * Member/Bot permission change + * * @see BotJoinGroupEvent * @see MemberJoinEvent * @@ -47,10 +50,13 @@ import net.mamoe.mirai.utils.read * * @see MemberPermissionChangeEvent * @see BotGroupPermissionChangeEvent + * + * @see BotInvitedJoinGroupRequestEvent + * @see MemberJoinRequestEvent */ internal class GroupListNoticeProcessor( - private val logger: MiraiLogger -) : MixedNoticeProcessor(), GroupEventProcessorContext { + private val logger: MiraiLogger, +) : MixedNoticeProcessor(), NewContactSupport { override suspend fun PipelineContext.processImpl(data: MsgType0x210) { if (data.uSubMsgType != 0x44L) return @@ -60,7 +66,7 @@ internal class GroupListNoticeProcessor( when (msg.msgGroupMsgSync.msgType) { 1, 2 -> { bot.components[ContactUpdater].groupListModifyLock.withLock { - bot.createGroupForBot(Mirai.calculateGroupUinByGroupCode(msg.msgGroupMsgSync.grpCode))?.let { + bot.addNewGroupByCode(msg.msgGroupMsgSync.grpCode)?.let { collect(BotJoinGroupEvent.Active(it)) } } @@ -85,10 +91,10 @@ internal class GroupListNoticeProcessor( 1 -> { val dataList = message.parseToMessageDataList() val invitor = dataList.first().let { messageData -> - group.getOrFail(messageData.data.toLong()) + group[messageData.data.toLong()] ?: return } val member = dataList.last().let { messageData -> - group.addNewNormalMember(messageData.toMemberInfo()) + group.addNewNormalMember(messageData.toMemberInfo()) ?: return } collect(MemberJoinEvent.Invite(member, invitor)) } @@ -116,50 +122,73 @@ internal class GroupListNoticeProcessor( * @see BotJoinGroupEvent.Active */ override suspend fun PipelineContext.processImpl(data: MsgComm.Msg) = data.context { - if (data.msgHead.msgType != 33) return bot.components[ContactUpdater].groupListModifyLock.withLock { - msgBody.msgContent.read { - val groupUin = Mirai.calculateGroupUinByGroupCode(readUInt().toLong()) - val group = - bot.getGroupByUin(groupUin) ?: bot.createGroupForBot(groupUin) ?: return markAsConsumed() - discardExact(1) - val joinedMemberUin = readUInt().toLong() - val joinType = readByte().toInt() - val invitorUin = readUInt().toLong() - when (joinType) { - // 邀请加入 - -125, 3 -> { - val invitor = group[invitorUin] ?: return markAsConsumed() - collected += if (joinedMemberUin == bot.id) { - BotJoinGroupEvent.Invite(invitor) - } else { - MemberJoinEvent.Invite(group.addNewNormalMember(getNewMemberInfo()), invitor) - } - } - // 通过群员分享的二维码/直接加入 - -126, 2 -> { - collected += if (joinedMemberUin == bot.id) { - BotJoinGroupEvent.Active(group) - } else { - MemberJoinEvent.Active(group.addNewNormalMember(getNewMemberInfo())) - } - } - // 忽略 - else -> { + when (data.msgHead.msgType) { + 33 -> processGroupJoin33(data) + 34 -> Unit // 34 与 33 重复, 忽略 34 + 38 -> processGroupJoin38(data) + 85 -> processGroupJoin85(data) + else -> return + } + markAsConsumed() + } + } + + // 33 + private suspend fun PipelineContext.processGroupJoin33(data: MsgComm.Msg) = data.context { + msgBody.msgContent.read { + val groupUin = Mirai.calculateGroupUinByGroupCode(readUInt().toLong()) + val group = bot.getGroupByUin(groupUin) ?: bot.addNewGroupByUin(groupUin) ?: return + discardExact(1) + val joinedMemberUin = readUInt().toLong() + val joinType = readByte().toInt() + val invitorUin = readUInt().toLong() + when (joinType) { + // 邀请加入 + -125, 3 -> { + val invitor = group[invitorUin] ?: return + collected += if (joinedMemberUin == bot.id) { + BotJoinGroupEvent.Invite(invitor) + } else { + MemberJoinEvent.Invite(group.addNewNormalMember(getNewMemberInfo()) ?: return, invitor) } } + // 通过群员分享的二维码/直接加入 + -126, 2 -> { + collected += if (joinedMemberUin == bot.id) { + BotJoinGroupEvent.Active(group) + } else { + MemberJoinEvent.Active(group.addNewNormalMember(getNewMemberInfo()) ?: return) + } + } + // 忽略 + else -> { + } } - // 邀请入群 - // package: 27 0B 60 E7 01 CA CC 69 8B 83 44 71 47 90 06 B9 DC C0 ED D4 B1 00 30 33 44 30 42 38 46 30 39 37 32 38 35 43 34 31 38 30 33 36 41 34 36 31 36 31 35 32 37 38 46 46 43 30 41 38 30 36 30 36 45 38 31 43 39 41 34 38 37 - // package: groupUin + 01 CA CC 69 8B 83 + invitorUin + length(06) + string + magicKey - - - // 主动入群, 直接加入: msgContent=27 0B 60 E7 01 76 E4 B8 DD 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 42 39 41 30 33 45 38 34 30 39 34 42 46 30 45 32 45 38 42 31 43 43 41 34 32 42 38 42 44 42 35 34 44 42 31 44 32 32 30 46 30 38 39 46 46 35 41 38 - // 主动直接加入 27 0B 60 E7 01 76 E4 B8 DD 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 33 30 45 38 42 31 33 46 41 41 31 33 46 38 31 35 34 41 38 33 32 37 31 43 34 34 38 35 33 35 46 45 31 38 32 43 39 42 43 46 46 32 44 39 39 46 41 37 - - // 有人被邀请(经过同意后)加入 27 0B 60 E7 01 76 E4 B8 DD 83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 34 30 34 38 32 33 38 35 37 41 37 38 46 33 45 37 35 38 42 39 38 46 43 45 44 43 32 41 30 31 36 36 30 34 31 36 39 35 39 30 38 39 30 39 45 31 34 34 - // 搜索到群, 直接加入 27 0B 60 E7 01 07 6E 47 BA 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 32 30 39 39 42 39 41 46 32 39 41 35 42 33 46 34 32 30 44 36 44 36 39 35 44 38 45 34 35 30 46 30 45 30 38 45 31 41 39 42 46 46 45 32 30 32 34 35 } + // 邀请入群 + // package: 27 0B 60 E7 01 CA CC 69 8B 83 44 71 47 90 06 B9 DC C0 ED D4 B1 00 30 33 44 30 42 38 46 30 39 37 32 38 35 43 34 31 38 30 33 36 41 34 36 31 36 31 35 32 37 38 46 46 43 30 41 38 30 36 30 36 45 38 31 43 39 41 34 38 37 + // package: groupUin + 01 CA CC 69 8B 83 + invitorUin + length(06) + string + magicKey + + + // 主动入群, 直接加入: msgContent=27 0B 60 E7 01 76 E4 B8 DD 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 42 39 41 30 33 45 38 34 30 39 34 42 46 30 45 32 45 38 42 31 43 43 41 34 32 42 38 42 44 42 35 34 44 42 31 44 32 32 30 46 30 38 39 46 46 35 41 38 + // 主动直接加入 27 0B 60 E7 01 76 E4 B8 DD 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 33 30 45 38 42 31 33 46 41 41 31 33 46 38 31 35 34 41 38 33 32 37 31 43 34 34 38 35 33 35 46 45 31 38 32 43 39 42 43 46 46 32 44 39 39 46 41 37 + + // 有人被邀请(经过同意后)加入 27 0B 60 E7 01 76 E4 B8 DD 83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 34 30 34 38 32 33 38 35 37 41 37 38 46 33 45 37 35 38 42 39 38 46 43 45 44 43 32 41 30 31 36 36 30 34 31 36 39 35 39 30 38 39 30 39 45 31 34 34 + // 搜索到群, 直接加入 27 0B 60 E7 01 07 6E 47 BA 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 32 30 39 39 42 39 41 46 32 39 41 35 42 33 46 34 32 30 44 36 44 36 39 35 44 38 45 34 35 30 46 30 45 30 38 45 31 41 39 42 46 46 45 32 30 32 34 35 + } + + // 38 + private suspend fun PipelineContext.processGroupJoin38(data: MsgComm.Msg) = data.context { + if (bot.getGroupByUin(msgHead.fromUin) != null) return + bot.addNewGroupByUin(msgHead.fromUin)?.let { collect(BotJoinGroupEvent.Active(it)) } + } + + // 85 + private suspend fun PipelineContext.processGroupJoin85(data: MsgComm.Msg) = data.context { + // msgHead.authUin: 处理人 + if (msgHead.toUin != bot.id) return + processGroupJoin38(data) } /////////////////////////////////////////////////////////////////////////// @@ -201,10 +230,10 @@ internal class GroupListNoticeProcessor( } 2 -> { // 被邀请入群, 自动同意, 不需处理 -// val group = bot.getNewGroup(groupCode) ?: return null -// val invitor = group[actionUin] -// -// BotJoinGroupEvent.Invite(invitor) + // val group = bot.getNewGroup(groupCode) ?: return null + // val invitor = group[actionUin] + // + // BotJoinGroupEvent.Invite(invitor) } 3 -> { // 已被请他管理员处理 } @@ -250,6 +279,13 @@ internal class GroupListNoticeProcessor( markAsConsumed() when (data.msgType) { 44 -> data.msgData.read { + // 3D C4 33 DD 01 FF CD 76 F4 03 C3 7E 2E 34 + // 群转让 + // start with 3D C4 33 DD 01 FF + // 3D C4 33 DD 01 FF C3 7E 2E 34 CD 76 F4 03 + // 权限变更 + // 3D C4 33 DD 01 00/01 ..... + // 3D C4 33 DD 01 01 C3 7E 2E 34 01 discardExact(5) val kind = readUByte().toInt() if (kind == 0xFF) { @@ -327,7 +363,7 @@ toUin=0x0000000026BA1173(649728371) target: Long, kind: Int, operator: Long, - groupUin: Long + groupUin: Long, ) { when (kind) { 2, 0x82 -> bot.getGroupByUin(groupUin)?.let { group -> @@ -367,7 +403,7 @@ toUin=0x0000000026BA1173(649728371) private fun PipelineContext.handlePermissionChange( data: OnlinePushTrans.PbMsgInfo, target: Long, - newPermissionByte: Int + newPermissionByte: Int, ) { val group = bot.getGroupByUin(data.fromUin) ?: return @@ -410,7 +446,7 @@ toUin=0x0000000026BA1173(649728371) // member Retrieve or permission changed to OWNER var newOwner = group[to] if (newOwner == null) { - newOwner = group.addNewNormalMember(MemberInfoImpl(uin = to, nick = "", permission = OWNER)) + newOwner = group.addNewNormalMember(MemberInfoImpl(uin = to, nick = "", permission = OWNER)) ?: return collect(MemberJoinEvent.Retrieve(newOwner)) } else if (newOwner.permission != OWNER) { collect(MemberPermissionChangeEvent(newOwner, newOwner.permission, OWNER)) @@ -421,7 +457,7 @@ toUin=0x0000000026BA1173(649728371) // bot Retrieve or permission changed to OWNER if (group == null) { - collect(BotJoinGroupEvent.Retrieve(bot.createGroupForBot(data.fromUin)!!)) + collect(BotJoinGroupEvent.Retrieve(bot.addNewGroupByUin(data.fromUin) ?: return)) return } diff --git a/mirai-core/src/commonMain/kotlin/network/notice/GroupMessageProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/GroupMessageProcessor.kt index 4a8ff2834..e10e1414d 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/GroupMessageProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/GroupMessageProcessor.kt @@ -35,6 +35,9 @@ import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.utils.* +/** + * Handles [GroupMessageEvent]. For private message events, see [PrivateMessageNoticeProcessor] + */ internal class GroupMessageProcessor : SimpleNoticeProcessor<MsgOnlinePush.PbPushMsg>(type()) { internal data class SendGroupMessageReceipt( val messageRandom: Int, @@ -61,7 +64,7 @@ internal class GroupMessageProcessor : SimpleNoticeProcessor<MsgOnlinePush.PbPus } - override suspend fun PipelineContext.process0(data: MsgOnlinePush.PbPushMsg) { + override suspend fun PipelineContext.processImpl(data: MsgOnlinePush.PbPushMsg) { val msgHead = data.msg.msgHead val isFromSelfAccount = msgHead.fromUin == bot.id @@ -73,13 +76,7 @@ internal class GroupMessageProcessor : SimpleNoticeProcessor<MsgOnlinePush.PbPus // 3116=group music share // 2021=group file // message sent by bot - collect( - SendGroupMessageReceipt( - messageRandom, - msgHead.msgSeq, - msgHead.fromAppid - ) - ) + collect(SendGroupMessageReceipt(messageRandom, msgHead.msgSeq, msgHead.fromAppid)) return } // else: sync form other device @@ -132,7 +129,7 @@ internal class GroupMessageProcessor : SimpleNoticeProcessor<MsgOnlinePush.PbPus group = group, sender = sender, senderName = nameCard.nick, - ) + ), ) return } else { @@ -145,8 +142,8 @@ internal class GroupMessageProcessor : SimpleNoticeProcessor<MsgOnlinePush.PbPus sender = sender, message = msgs.map { it.msg }.toMessageChainOnline(bot, group.id, MessageSourceKind.GROUP), permission = sender.permission, - time = msgHead.msgTime - ) + time = msgHead.msgTime, + ), ) return } @@ -154,7 +151,7 @@ internal class GroupMessageProcessor : SimpleNoticeProcessor<MsgOnlinePush.PbPus private suspend inline fun broadcastNameCardChangedEventIfNecessary( sender: Member, - new: MemberNick + new: MemberNick, ) { if (sender is NormalMemberImpl) { val currentNameCard = sender.nameCard @@ -177,7 +174,7 @@ internal class GroupMessageProcessor : SimpleNoticeProcessor<MsgOnlinePush.PbPus private fun findSenderName( extraInfo: ImMsgBody.ExtraInfo?, - groupInfo: MsgComm.GroupInfo + groupInfo: MsgComm.GroupInfo, ): MemberNick? = extraInfo?.groupCard?.takeIf { it.isNotEmpty() }?.decodeCommCardNameBuf()?.let { MemberNick(it, true) diff --git a/mirai-core/src/commonMain/kotlin/network/notice/NewContactSupport.kt b/mirai-core/src/commonMain/kotlin/network/notice/NewContactSupport.kt new file mode 100644 index 000000000..ac829ca40 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/notice/NewContactSupport.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2019-2021 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/master/LICENSE + */ + +package net.mamoe.mirai.internal.network.notice + +import kotlinx.coroutines.cancel +import net.mamoe.mirai.Mirai +import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.contact.FriendImpl +import net.mamoe.mirai.internal.contact.GroupImpl +import net.mamoe.mirai.internal.contact.StrangerImpl +import net.mamoe.mirai.internal.contact.impl +import net.mamoe.mirai.internal.contact.info.FriendInfoImpl +import net.mamoe.mirai.internal.contact.info.GroupInfoImpl +import net.mamoe.mirai.internal.contact.info.MemberInfoImpl +import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl +import net.mamoe.mirai.internal.getGroupByUin +import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopNum +import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList +import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect + +internal interface NewContactSupport { + + fun MsgComm.Msg.getNewMemberInfo(): MemberInfoImpl { + return MemberInfoImpl( + nameCard = msgHead.authNick.ifEmpty { msgHead.fromNick }, + permission = MemberPermission.MEMBER, + specialTitle = "", + muteTimestamp = 0, + uin = msgHead.authUin, + nick = msgHead.authNick.ifEmpty { msgHead.fromNick }, + remark = "", + anonymousId = null, + ) + } + + suspend fun QQAndroidBot.addNewGroupByCode(code: Long): GroupImpl? { + if (getGroup(code) != null) return null + return getNewGroup(code)?.apply { groups.delegate.add(this) } + } + + suspend fun QQAndroidBot.addNewGroupByUin(groupUin: Long): GroupImpl? { + if (getGroupByUin(groupUin) != null) return null + return getNewGroup(Mirai.calculateGroupCodeByGroupUin(groupUin))?.apply { groups.delegate.add(this) } + } + + suspend fun QQAndroidBot.addNewGroup(stTroopNum: StTroopNum): GroupImpl? { + if (getGroup(stTroopNum.groupCode) != null) return null + return getNewGroup(stTroopNum)?.apply { groups.delegate.add(this) } + } + + fun QQAndroidBot.removeStranger(id: Long): StrangerImpl? { + val instance = strangers[id] ?: return null + strangers.remove(instance.id) + instance.cancel() + return instance + } + + fun QQAndroidBot.removeFriend(id: Long): FriendImpl? { + val instance = friends[id] ?: return null + friends.remove(instance.id) + instance.cancel() + return instance + } + + fun QQAndroidBot.addNewFriendAndRemoveStranger(info: FriendInfoImpl): FriendImpl? { + if (friends.contains(info.uin)) return null + strangers[info.uin]?.let { removeStranger(it.id) } + val friend = Mirai.newFriend(bot, info).impl() + friends.delegate.add(friend) + return friend + } + + fun QQAndroidBot.addNewStranger(info: StrangerInfoImpl): StrangerImpl? { + if (friends.contains(info.uin)) return null // cannot have both stranger and friend + if (strangers.contains(info.uin)) return null + val stranger = Mirai.newStranger(bot, info).impl() + strangers.delegate.add(stranger) + return stranger + } + + private suspend fun QQAndroidBot.getNewGroup(groupCode: Long): GroupImpl? { + val troopNum = FriendList.GetTroopListSimplify(client) + .sendAndExpect(network, timeoutMillis = 10_000, retry = 5) + .groups.firstOrNull { it.groupCode == groupCode } ?: return null + + return getNewGroup(troopNum) + } + + private suspend fun QQAndroidBot.getNewGroup(troopNum: StTroopNum): GroupImpl? { + return GroupImpl( + bot = this, + parentCoroutineContext = coroutineContext, + id = troopNum.groupCode, + groupInfo = GroupInfoImpl(troopNum), + members = Mirai.getRawGroupMemberList( + this, + troopNum.groupUin, + troopNum.groupCode, + troopNum.dwGroupOwnerUin, + ), + ) + } + +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/notice/OtherClientNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/OtherClientNoticeProcessor.kt new file mode 100644 index 000000000..ad8d6622d --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/notice/OtherClientNoticeProcessor.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2019-2021 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/master/LICENSE + */ + +package net.mamoe.mirai.internal.network.notice + +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.sync.withLock +import net.mamoe.mirai.Mirai +import net.mamoe.mirai.contact.ClientKind +import net.mamoe.mirai.contact.OtherClientInfo +import net.mamoe.mirai.contact.Platform +import net.mamoe.mirai.event.events.OtherClientMessageEvent +import net.mamoe.mirai.event.events.OtherClientOfflineEvent +import net.mamoe.mirai.event.events.OtherClientOnlineEvent +import net.mamoe.mirai.internal.contact.appId +import net.mamoe.mirai.internal.contact.createOtherClient +import net.mamoe.mirai.internal.message.OnlineMessageSourceFromFriendImpl +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.PipelineContext +import net.mamoe.mirai.internal.network.handler.logger +import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushStatus +import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.internal.network.protocol.data.proto.SubMsgType0x7 +import net.mamoe.mirai.internal.utils._miraiContentToString +import net.mamoe.mirai.internal.utils.io.serialization.loadAs +import net.mamoe.mirai.message.data.PlainText +import net.mamoe.mirai.message.data.buildMessageChain +import net.mamoe.mirai.utils.context + +/** + * @see OtherClientOnlineEvent + * @see OtherClientOfflineEvent + * + * @see OtherClientMessageEvent + */ +internal class OtherClientNoticeProcessor : MixedNoticeProcessor() { + /** + * @see OtherClientOnlineEvent + * @see OtherClientOfflineEvent + */ + override suspend fun PipelineContext.processImpl(data: RequestPushStatus) { + markAsConsumed() + bot.components[ContactUpdater].otherClientsLock.withLock { + val instanceInfo = data.vecInstanceList?.firstOrNull() + val appId = instanceInfo?.iAppId ?: 1 + when (data.status.toInt()) { + 1 -> { // online + if (bot.otherClients.any { appId == it.appId }) return + + suspend fun tryFindInQuery(): OtherClientInfo? { + return Mirai.getOnlineOtherClientsList(bot).find { it.appId == appId } + ?: kotlin.run { + delay(2000) // sometimes server sync slow + Mirai.getOnlineOtherClientsList(bot).find { it.appId == appId } + } + } + + val info = + tryFindInQuery() ?: kotlin.run { + bot.network.logger.warning( + contextualBugReportException( + "SvcRequestPushStatus (OtherClient online)", + "packet: \n" + data._miraiContentToString() + + "\n\nquery: \n" + + Mirai.getOnlineOtherClientsList(bot)._miraiContentToString(), + additional = "Failed to find corresponding instanceInfo.", + ), + ) + OtherClientInfo(appId, Platform.WINDOWS, "", "电脑") + } + + val client = bot.createOtherClient(info) + bot.otherClients.delegate.add(client) + collected += OtherClientOnlineEvent( + client, + ClientKind[data.nClientType?.toInt() ?: 0], + ) + } + + 2 -> { // off + val client = bot.otherClients.find { it.appId == appId } ?: return + client.cancel(CancellationException("Offline")) + bot.otherClients.delegate.remove(client) + collected += OtherClientOfflineEvent(client) + } + + else -> throw contextualBugReportException( + "decode SvcRequestPushStatus (PC Client status change)", + data._miraiContentToString(), + additional = "unknown status=${data.status}", + ) + } + } + } + + + /** + * @see OtherClientMessageEvent + */ + override suspend fun PipelineContext.processImpl(data: MsgComm.Msg) = data.context { + if (msgHead.msgType != 529) return + markAsConsumed() // todo check + if (msgHead.c2cCmd != 7) return + val body = msgBody.msgContent.loadAs(SubMsgType0x7.MsgBody.serializer()) + + val textMsg = + body.msgSubcmd0x4Generic?.buf?.loadAs(SubMsgType0x7.MsgBody.QQDataTextMsg.serializer()) + ?: return + + with(body.msgHeader ?: return) { + if (dstUin != bot.id) return + val client = bot.otherClients.find { it.appId == srcInstId } + ?: return // don't compare with dstAppId. diff. + + val chain = buildMessageChain { + +OnlineMessageSourceFromFriendImpl(bot, listOf(data)) + for (msgItem in textMsg.msgItems) { + when (msgItem.type) { + 1 -> +PlainText(msgItem.text) + else -> { + } + } + } + } + + collect(OtherClientMessageEvent(client, chain, msgHead.msgTime)) + } + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/notice/PrivateMessageNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/PrivateMessageNoticeProcessor.kt new file mode 100644 index 000000000..33a86edf9 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/notice/PrivateMessageNoticeProcessor.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2019-2021 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/master/LICENSE + */ + +package net.mamoe.mirai.internal.network.notice + +import net.mamoe.mirai.contact.User +import net.mamoe.mirai.event.events.* +import net.mamoe.mirai.internal.contact.* +import net.mamoe.mirai.internal.getGroupByUin +import net.mamoe.mirai.internal.message.toMessageChainOnline +import net.mamoe.mirai.internal.network.components.PipelineContext +import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor +import net.mamoe.mirai.internal.network.components.SsoProcessor +import net.mamoe.mirai.internal.network.notice.SystemMessageProcessor.Companion.fromSync +import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.utils.context + +/** + * Handles [UserMessageEvent] and their sync events. For [GroupMessageEvent], see [GroupMessageProcessor] + * + * @see StrangerMessageEvent + * @see StrangerMessageSyncEvent + * + * @see FriendMessageEvent + * @see FriendMessageSyncEvent + * + * @see GroupTempMessageEvent + * @see GroupTempMessageSyncEvent + */ +internal class PrivateMessageNoticeProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type()) { + override suspend fun PipelineContext.processImpl(data: MsgComm.Msg) = data.context { + if (msgHead.fromUin == bot.id && fromSync) { + // Bot send message to himself? or from other client? I am not the implementer. + bot.client.sendFriendMessageSeq.updateIfSmallerThan(msgHead.msgSeq) + return + } + if (!bot.components[SsoProcessor].firstLoginSucceed) return + val senderUin = if (fromSync) msgHead.toUin else msgHead.fromUin + when (msgHead.msgType) { + 166, 167, // 单向好友 + 208, // friend ptt, maybe also support stranger + -> { + handlePrivateMessage(data, bot.getFriend(senderUin) ?: bot.getStranger(senderUin) ?: return) + markAsConsumed() + } + + 141, // group temp + -> { + val tmpHead = msgHead.c2cTmpMsgHead ?: return + val group = bot.getGroupByUin(tmpHead.groupUin) ?: return + handlePrivateMessage(data, group[senderUin] ?: return) + markAsConsumed() + } + } + + } + + private suspend fun PipelineContext.handlePrivateMessage( + data: MsgComm.Msg, + user: User, + ) = data.context { + user.impl() + if (!user.messageSeq.updateIfDifferentWith(msgHead.msgSeq)) return + if (contentHead?.autoReply == 1) return + + val msgs = user.fragmentedMessageMerger.tryMerge(this) + if (msgs.isEmpty()) return + + val chain = msgs.toMessageChainOnline(bot, 0, user.correspondingMessageSourceKind) + val time = msgHead.msgTime + + collected += if (fromSync) { + when (user) { + is FriendImpl -> FriendMessageSyncEvent(user, chain, time) + is StrangerImpl -> StrangerMessageSyncEvent(user, chain, time) + is NormalMemberImpl -> GroupTempMessageSyncEvent(user, chain, time) + else -> null + } + } else { + when (user) { + is FriendImpl -> FriendMessageEvent(user, chain, time) + is StrangerImpl -> StrangerMessageEvent(user, chain, time) + is NormalMemberImpl -> GroupTempMessageEvent(user, chain, time) + else -> null + } + } ?: error("unreachable") + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/notice/SystemMessageProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/SystemMessageProcessor.kt index d9ffb3c4f..f36b71e20 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/SystemMessageProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/SystemMessageProcessor.kt @@ -10,39 +10,24 @@ package net.mamoe.mirai.internal.network.notice import kotlinx.coroutines.sync.withLock -import kotlinx.io.core.discardExact -import kotlinx.io.core.readUByte -import kotlinx.io.core.readUShort -import net.mamoe.mirai.Mirai -import net.mamoe.mirai.event.broadcast -import net.mamoe.mirai.event.events.* -import net.mamoe.mirai.internal.contact.* -import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl -import net.mamoe.mirai.internal.getGroupByUin -import net.mamoe.mirai.internal.message.OnlineMessageSourceFromFriendImpl -import net.mamoe.mirai.internal.message.toMessageChainOnline import net.mamoe.mirai.internal.network.components.ContactUpdater import net.mamoe.mirai.internal.network.components.MsgCommonMsgProcessor import net.mamoe.mirai.internal.network.components.PipelineContext import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.handler.logger -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.SubMsgType0x7 import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact -import net.mamoe.mirai.internal.utils.io.serialization.loadAs -import net.mamoe.mirai.message.data.MessageSourceKind -import net.mamoe.mirai.message.data.PlainText -import net.mamoe.mirai.message.data.buildMessageChain -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.TypeKey +import net.mamoe.mirai.utils.debug +import net.mamoe.mirai.utils.toUHexString -internal class SystemMessageProcessor : MsgCommonMsgProcessor(), GroupEventProcessorContext { +internal class SystemMessageProcessor : MsgCommonMsgProcessor(), NewContactSupport { companion object { val KEY_FROM_SYNC = TypeKey<Boolean>("fromSync") val PipelineContext.fromSync get() = attributes[KEY_FROM_SYNC] } - override suspend fun PipelineContext.process0(data: MsgComm.Msg): Unit = data.run { + override suspend fun PipelineContext.processImpl(data: MsgComm.Msg): Unit = data.run { // TODO: 2021/6/26 extract logic into multiple processors when (msgHead.msgType) { 33 -> bot.components[ContactUpdater].groupListModifyLock.withLock { @@ -53,19 +38,11 @@ internal class SystemMessageProcessor : MsgCommonMsgProcessor(), GroupEventProce } 38 -> bot.components[ContactUpdater].groupListModifyLock.withLock { // 建群 - bot.createGroupForBot(msgHead.fromUin) - ?.let { collect(BotJoinGroupEvent.Active(it)) } - return + TODO("removed") } 85 -> bot.components[ContactUpdater].groupListModifyLock.withLock { // 其他客户端入群 - // msgHead.authUin: 处理人 - - if (msgHead.toUin == bot.id) { - bot.createGroupForBot(msgHead.fromUin) - ?.let { collect(BotJoinGroupEvent.Active(it)) } - } - return + TODO("removed") } /* @@ -116,92 +93,11 @@ internal class SystemMessageProcessor : MsgCommonMsgProcessor(), GroupEventProce //167 单向好友 166, 167 -> { - //我也不知道为什么要这样写,但它就是能跑 - if (msgHead.fromUin == bot.id && fromSync) { - loop@ while (true) { - val instance = bot.client.getFriendSeq() - if (instance < msgHead.msgSeq) { - if (bot.client.setFriendSeq(instance, msgHead.msgSeq)) { - break@loop - } - } else break@loop - } - return - } - if (!bot.components[SsoProcessor].firstLoginSucceed) { - return - } - val fromUin = if (fromSync) { - msgHead.toUin - } else { - msgHead.fromUin - } - bot.getFriend(fromUin)?.let { friend -> - friend.checkIsFriendImpl() - friend.lastMessageSequence.loop { - //我也不知道为什么要这样写,但它就是能跑 - if (friend.lastMessageSequence.value != msgHead.msgSeq - && friend.lastMessageSequence.compareAndSet(it, msgHead.msgSeq) - && contentHead?.autoReply != 1 - ) { - val msgs = friend.friendPkgMsgParsingCache.tryMerge(this) - if (msgs.isNotEmpty()) { - collect( - if (fromSync) { - FriendMessageSyncEvent( - friend, - msgs.toMessageChainOnline(bot, 0, MessageSourceKind.FRIEND), - msgHead.msgTime - ) - } else { - FriendMessageEvent( - friend, - msgs.toMessageChainOnline(bot, 0, MessageSourceKind.FRIEND), - msgHead.msgTime - ) - } - ) - } else return - } - return - } - } ?: bot.getStranger(fromUin)?.let { stranger -> - stranger.checkIsImpl() - stranger.lastMessageSequence.loop { - //我也不知道为什么要这样写,但它就是能跑 - if (stranger.lastMessageSequence.value != msgHead.msgSeq && stranger.lastMessageSequence.compareAndSet( - it, - msgHead.msgSeq - ) && contentHead?.autoReply != 1 - ) { - collect( - if (fromSync) { - StrangerMessageSyncEvent( - stranger, - listOf(this).toMessageChainOnline(bot, 0, MessageSourceKind.STRANGER), - msgHead.msgTime - ) - } else { - StrangerMessageEvent( - stranger, - listOf(this).toMessageChainOnline(bot, 0, MessageSourceKind.STRANGER), - msgHead.msgTime - ) - } - ) - } - return - } - } - return + TODO("removed") } 208 -> { // friend ptt - val target = bot.getFriend(msgHead.fromUin) ?: return - val lsc = listOf(this).toMessageChainOnline(bot, 0, MessageSourceKind.FRIEND) - - collect(FriendMessageEvent(target, lsc, msgHead.msgTime)) - return + TODO("removed") } 529 -> { @@ -210,30 +106,7 @@ internal class SystemMessageProcessor : MsgCommonMsgProcessor(), GroupEventProce when (msgHead.c2cCmd) { // other client sync 7 -> { - val body = msgBody.msgContent.loadAs(SubMsgType0x7.MsgBody.serializer()) - - val textMsg = - body.msgSubcmd0x4Generic?.buf?.loadAs(SubMsgType0x7.MsgBody.QQDataTextMsg.serializer()) - ?: return - - with(body.msgHeader ?: return) { - if (dstUin != bot.id) return - val client = bot.otherClients.find { it.appId == srcInstId } - ?: return// don't compare with dstAppId. diff. - - val chain = buildMessageChain { - +OnlineMessageSourceFromFriendImpl(bot, listOf(data)) - for (msgItem in textMsg.msgItems) { - when (msgItem.type) { - 1 -> +PlainText(msgItem.text) - else -> { - } - } - } - } - - collect(OtherClientMessageEvent(client, chain, msgHead.msgTime)) - } + TODO("removed") } } @@ -245,39 +118,7 @@ internal class SystemMessageProcessor : MsgCommonMsgProcessor(), GroupEventProce if (!bot.components[SsoProcessor].firstLoginSucceed || msgHead.fromUin == bot.id && !fromSync) { return } - val tmpHead = msgHead.c2cTmpMsgHead ?: return - val member = bot.getGroupByUin(tmpHead.groupUin)?.get( - if (fromSync) { - msgHead.toUin - } else { - msgHead.fromUin - } - ) - ?: return - - member.checkIsMemberImpl() - - member.lastMessageSequence.loop { instant -> - if (member.lastMessageSequence.value != msgHead.msgSeq && contentHead?.autoReply != 1) { - if (member.lastMessageSequence.compareAndSet(instant, msgHead.msgSeq)) { - collect( - if (fromSync) { - GroupTempMessageSyncEvent( - member, - listOf(this).toMessageChainOnline(bot, 0, MessageSourceKind.TEMP), - msgHead.msgTime - ) - } else { - GroupTempMessageEvent( - member, - listOf(this).toMessageChainOnline(bot, 0, MessageSourceKind.TEMP), - msgHead.msgTime - ) - } - ) - } - } else return - } + TODO("removed") } 84, 87 -> { // 请求入群验证 和 被要求入群 bot.network.run { @@ -298,45 +139,7 @@ internal class SystemMessageProcessor : MsgCommonMsgProcessor(), GroupEventProce } //陌生人添加信息 191 -> { - var fromGroup = 0L - var pbNick = "" - msgBody.msgContent.read { - readUByte()// version - discardExact(readUByte().toInt())//skip - readUShort()//source id - readUShort()//SourceSubID - discardExact(readUShort().toLong())//skip size - if (readUShort().toInt() != 0) {//hasExtraInfo - discardExact(readUShort().toInt())//mail address info, skip - } - discardExact(4 + readUShort().toInt())//skip - for (i in 1..readUByte().toInt()) {//pb size - val type = readUShort().toInt() - val pbArray = ByteArray(readUShort().toInt() and 0xFF) - readAvailable(pbArray) - when (type) { - 1000 -> pbArray.loadAs(FrdSysMsg.GroupInfo.serializer()).let { fromGroup = it.groupUin } - 1002 -> pbArray.loadAs(FrdSysMsg.FriendMiscInfo.serializer()) - .let { pbNick = it.fromuinNick } - else -> { - }//ignore - } - } - } - val nick = - sequenceOf(msgHead.fromNick, msgHead.authNick, pbNick).filter { it.isNotEmpty() }.firstOrNull() - ?: return - val id = - sequenceOf(msgHead.fromUin, msgHead.authUin).filter { it != 0L }.firstOrNull() ?: return//对方QQ - Mirai.newStranger(bot, StrangerInfoImpl(id, nick, fromGroup)).checkIsImpl().let { - bot.getStranger(id)?.let { previous -> - bot.strangers.remove(id) - StrangerRelationChangeEvent.Deleted(previous).broadcast() - } - bot.strangers.delegate.add(it) - - collect(StrangerAddEvent(it)) - } + TODO("removed") } // 732: 27 0B 60 E7 0C 01 3E 03 3F A2 5E 90 60 E2 00 01 44 71 47 90 00 00 02 58 // 732: 27 0B 60 E7 11 00 40 08 07 20 E7 C1 AD B8 02 5A 36 08 B4 E7 E0 F0 09 1A 1A 08 9C D4 16 10 F7 D2 D8 F5 05 18 D0 E2 85 F4 06 20 00 28 00 30 B4 E7 E0 F0 09 2A 0E 08 00 12 0A 08 9C D4 16 10 00 18 01 20 00 30 00 38 00 diff --git a/mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgInfoDecoder.kt b/mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgInfoDecoder.kt index a7e379cc8..9c76c15f3 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgInfoDecoder.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgInfoDecoder.kt @@ -30,7 +30,7 @@ import net.mamoe.mirai.utils.toUHexString internal class MsgInfoDecoder( private val logger: MiraiLogger, ) : SimpleNoticeProcessor<MsgInfo>(type()) { - override suspend fun PipelineContext.process0(data: MsgInfo) { + override suspend fun PipelineContext.processImpl(data: MsgInfo) { when (data.shMsgType.toUShort().toInt()) { // 528 0x210 -> fire(data.vMsg.loadAs(MsgType0x210.serializer())) diff --git a/mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgType0x210Decoder.kt b/mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgType0x210Decoder.kt index 7b91a4de9..f5142a7d5 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgType0x210Decoder.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgType0x210Decoder.kt @@ -14,7 +14,7 @@ import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 internal class MsgType0x210Decoder : SimpleNoticeProcessor<MsgType0x210>(type()) { - override suspend fun PipelineContext.process0(data: MsgType0x210) { + override suspend fun PipelineContext.processImpl(data: MsgType0x210) { when (data.uSubMsgType) { 0x8AL -> { } diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/ReqPushStatus.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/ReqPushStatus.kt index 3ccac2f02..145cefb7d 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/ReqPushStatus.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/ReqPushStatus.kt @@ -10,6 +10,7 @@ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable +import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId @@ -25,6 +26,5 @@ internal class RequestPushStatus( @JvmField @TarsId(6) val nClientType: Long? = null, @JvmField @TarsId(7) val nInstanceId: Long? = null, @JvmField @TarsId(8) val vecInstanceList: List<InstanceInfo>? = null, -) : JceStruct - +) : JceStruct, Packet diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/msgType0x210.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/msgType0x210.kt index 8e53e9912..95db783d1 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/msgType0x210.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/msgType0x210.kt @@ -952,7 +952,7 @@ internal class Submsgtype0x27 { @Serializable internal class ProfileInfo( @ProtoNumber(1) @JvmField val field: Int = 0, - @ProtoNumber(2) @JvmField val value: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoNumber(2) @JvmField val value: String = "", ) : ProtoBuf @Serializable diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt index dce8b1e51..c1d737596 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt @@ -13,6 +13,7 @@ import kotlinx.io.core.ByteReadPacket import net.mamoe.mirai.event.Event import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet +import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.noticeProcessorPipeline import net.mamoe.mirai.internal.network.components.PacketCodec import net.mamoe.mirai.internal.network.protocol.packet.chat.* import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore @@ -27,7 +28,9 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.Heartbeat import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard +import net.mamoe.mirai.internal.network.toPacket import net.mamoe.mirai.utils.MiraiLoggerWithSwitch +import net.mamoe.mirai.utils.TypeSafeMap internal sealed class PacketFactory<TPacket : Packet?> { /** @@ -36,6 +39,17 @@ internal sealed class PacketFactory<TPacket : Packet?> { abstract val receivingCommandName: String open val canBeCached: Boolean get() = true + + protected companion object { + + @JvmStatic + suspend fun QQAndroidBot.processPacketThroughPipeline( + data: Any?, + attributes: TypeSafeMap = TypeSafeMap(), + ): Packet { + return components.noticeProcessorPipeline.process(this, data, attributes).toPacket() + } + } } /** diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt index fc5f801a8..f821273e1 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt @@ -264,7 +264,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg. sequenceIds = sequenceIds, randIds = randIds, sequenceIdsInitializer = { size -> - IntArray(size) { client.nextFriendSeq() } + IntArray(size) { client.sendFriendMessageSeq.next() } }, postInit = { sourceCallback( @@ -278,7 +278,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg. ) ) }, - doFragmented = fragmented + doFragmented = fragmented, ) } /*= buildOutgoingUniPacket(client) { diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.RequestPushStatus.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.RequestPushStatus.kt index 5e76e1ced..79e8e4be1 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.RequestPushStatus.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.RequestPushStatus.kt @@ -9,84 +9,18 @@ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay -import kotlinx.coroutines.sync.withLock import kotlinx.io.core.ByteReadPacket -import net.mamoe.mirai.Mirai -import net.mamoe.mirai.contact.ClientKind -import net.mamoe.mirai.contact.OtherClientInfo -import net.mamoe.mirai.contact.Platform -import net.mamoe.mirai.event.events.OtherClientOfflineEvent -import net.mamoe.mirai.event.events.OtherClientOnlineEvent import net.mamoe.mirai.internal.QQAndroidBot -import net.mamoe.mirai.internal.contact.appId -import net.mamoe.mirai.internal.contact.createOtherClient -import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.network.Packet -import net.mamoe.mirai.internal.network.components.ContactUpdater -import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushStatus import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory -import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.serialization.readUniPacket -internal object MessageSvcRequestPushStatus : IncomingPacketFactory<Packet?>( - "MessageSvc.RequestPushStatus", "" +internal object MessageSvcRequestPushStatus : IncomingPacketFactory<Packet>( + "MessageSvc.RequestPushStatus", "", ) { - override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? { + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet { val packet = readUniPacket(RequestPushStatus.serializer()) - bot.components[ContactUpdater].otherClientsLock.withLock { - val instanceInfo = packet.vecInstanceList?.firstOrNull() - val appId = instanceInfo?.iAppId ?: 1 - return when (packet.status.toInt()) { - 1 -> { // online - if (bot.otherClients.any { appId == it.appId }) return null - - suspend fun tryFindInQuery(): OtherClientInfo? { - return Mirai.getOnlineOtherClientsList(bot).find { it.appId == appId } - ?: kotlin.run { - delay(2000) // sometimes server sync slow - Mirai.getOnlineOtherClientsList(bot).find { it.appId == appId } - } - } - - val info = - tryFindInQuery() ?: kotlin.run { - bot.network.logger.warning( - contextualBugReportException( - "SvcRequestPushStatus (OtherClient online)", - "packet: \n" + packet._miraiContentToString() + - "\n\nquery: \n" + - Mirai.getOnlineOtherClientsList(bot)._miraiContentToString(), - additional = "Failed to find corresponding instanceInfo." - ) - ) - OtherClientInfo(appId, Platform.WINDOWS, "", "电脑") - } - - val client = bot.createOtherClient(info) - bot.otherClients.delegate.add(client) - OtherClientOnlineEvent( - client, - ClientKind[packet.nClientType?.toInt() ?: 0] - ) - } - - 2 -> { // off - val client = bot.otherClients.find { it.appId == appId } ?: return null - client.cancel(CancellationException("Offline")) - bot.otherClients.delegate.remove(client) - OtherClientOfflineEvent(client) - } - - else -> throw contextualBugReportException( - "decode SvcRequestPushStatus (PC Client status change)", - packet._miraiContentToString(), - additional = "unknown status=${packet.status}" - ) - } - } + return bot.processPacketThroughPipeline(packet) } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt index e925de0aa..6b44c0de2 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt @@ -17,15 +17,15 @@ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import kotlinx.io.core.* import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber -import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.contact.User import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.QQAndroidBot -import net.mamoe.mirai.internal.contact.* -import net.mamoe.mirai.internal.contact.info.FriendInfoImpl +import net.mamoe.mirai.internal.contact.GroupImpl +import net.mamoe.mirai.internal.contact.checkIsGroupImpl +import net.mamoe.mirai.internal.contact.checkIsMemberImpl import net.mamoe.mirai.internal.network.MultiPacketImpl import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient @@ -34,17 +34,12 @@ import net.mamoe.mirai.internal.network.protocol.data.jce.MsgInfo import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.jce.OnlinePushPack import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPacket -import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x115 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x122 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27.SubMsgType0x27.* -import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x44.Submsgtype0x44 -import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0xb3 import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857 import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.buildResponseUniPacket -import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList -import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.* @@ -538,20 +533,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf( 0x111L to ignoredLambda528, // 新好友 0xB3L to lambda528 { bot -> - // 08 01 12 52 08 A2 FF 8C F0 03 10 00 1D 15 3D 90 5E 22 2E E6 88 91 E4 BB AC E5 B7 B2 E7 BB 8F E6 98 AF E5 A5 BD E5 8F 8B E5 95 A6 EF BC 8C E4 B8 80 E8 B5 B7 E6 9D A5 E8 81 8A E5 A4 A9 E5 90 A7 21 2A 09 48 69 6D 31 38 38 6D 6F 65 30 07 38 03 48 DD F1 92 B7 07 - val body = vProtobuf.loadAs(Submsgtype0xb3.SubMsgType0xb3.MsgBody.serializer()) - val new = Mirai.newFriend( - bot, FriendInfoImpl( - uin = body.msgAddFrdNotify.fuin, - nick = body.msgAddFrdNotify.fuinNick, - remark = "", - ) - ).checkIsFriendImpl() - bot.friends.delegate.add(new) - return@lambda528 bot.getStranger(new.id)?.let { - bot.strangers.remove(new.id) - sequenceOf(StrangerRelationChangeEvent.Friended(it, new), FriendAddEvent(new)) - } ?: sequenceOf(FriendAddEvent(new)) + TODO("removed") }, 0xE2L to lambda528 { _ -> // TODO: unknown. maybe messages. @@ -561,35 +543,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf( return@lambda528 emptySequence() }, 0x44L to lambda528 { bot -> - val msg = vProtobuf.loadAs(Submsgtype0x44.MsgBody.serializer()) - - val packetList = mutableListOf<Packet>() - if (msg.msgFriendMsgSync != null) { - when (msg.msgFriendMsgSync.processtype) { - 3, 9, 10 -> { - if (bot.getFriend(msg.msgFriendMsgSync.fuin) == null) { - val response: FriendList.GetFriendGroupList.Response = - FriendList.GetFriendGroupList.forSingleFriend( - bot.client, - msg.msgFriendMsgSync.fuin - ).sendAndExpect(bot) - response.friendList.firstOrNull()?.let { - val friend = Mirai.newFriend(bot, it.toMiraiFriendInfo()).checkIsFriendImpl() - bot.friends.delegate.add(friend) - packetList.add(FriendAddEvent(friend)) - } - } - } - } - } - if (msg.msgGroupMsgSync != null) { - when (msg.msgGroupMsgSync.msgType) { - 1, 2 -> { - TODO("removed") - } - } - } - return@lambda528 packetList.asSequence() + TODO("removed") }, // bot 在其他客户端被踢或主动退出而同步情况 0xD4L to lambda528 { _ -> @@ -666,39 +620,10 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf( }, //好友输入状态 0x115L to lambda528 { bot -> - val body = vProtobuf.loadAs(Submsgtype0x115.SubMsgType0x115.MsgBody.serializer()) - val friend = bot.getFriend(body.fromUin) - val item = body.msgNotifyItem - return@lambda528 if (friend != null && item != null) { - sequenceOf(FriendInputStatusChangedEvent(friend, item.eventType == 1)) - } else { - emptySequence() - } + TODO("removed") }, // 群相关, ModFriendRemark, DelFriend, ModGroupProfile 0x27L to lambda528 { bot -> - fun ModFriendRemark.transform(bot: QQAndroidBot): Sequence<Packet> { - return this.msgFrdRmk.asSequence().mapNotNull { - val friend = bot.getFriend(it.fuin) ?: return@mapNotNull null - val old: String - friend.checkIsFriendImpl().friendInfo - .also { info -> old = info.remark } - .remark = it.rmkName - // TODO: 2020/4/10 ADD REMARK QUERY - FriendRemarkChangeEvent(friend, old, it.rmkName) - } - } - - fun DelFriend.transform(bot: QQAndroidBot): Sequence<Packet> { - return this.uint64Uins.asSequence().mapNotNull { - - val friend = bot.getFriend(it) ?: return@mapNotNull null - if (bot.friends.delegate.remove(friend)) { - FriendDeleteEvent(friend) - } else null - } - } - fun ModGroupProfile.transform(bot: QQAndroidBot): Sequence<Packet> { return this.msgGroupProfileInfos.asSequence().mapNotNull { info -> when (info.field) { @@ -729,23 +654,23 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf( var85.troopface = var3; var85.hasSetNewTroopHead = true; */ -// bot.logger.debug( -// contextualBugReportException( -// "解析 Transformers528 0x27L ModGroupProfile 群头像修改", -// forDebug = "this=${this._miraiContentToString()}" -// ) -// ) + // bot.logger.debug( + // contextualBugReportException( + // "解析 Transformers528 0x27L ModGroupProfile 群头像修改", + // forDebug = "this=${this._miraiContentToString()}" + // ) + // ) null } 3 -> { // troop.credit.data // top_package/akkz.java:3475 // top_package/akkz.java:3498 -// bot.logger.debug( -// contextualBugReportException( -// "解析 Transformers528 0x27L ModGroupProfile 群 troop.credit.data", -// forDebug = "this=${this._miraiContentToString()}" -// ) -// ) + // bot.logger.debug( + // contextualBugReportException( + // "解析 Transformers528 0x27L ModGroupProfile 群 troop.credit.data", + // forDebug = "this=${this._miraiContentToString()}" + // ) + // ) null } @@ -789,64 +714,15 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf( } } - fun ModCustomFace.transform(bot: QQAndroidBot): Sequence<Packet> { - if (uin == bot.id) { - return sequenceOf(BotAvatarChangedEvent(bot)) - } - val friend = bot.getFriend(uin) ?: return emptySequence() - return sequenceOf(FriendAvatarChangedEvent(friend)) - } - - fun ModProfile.transform(bot: QQAndroidBot): Sequence<Packet> = buildList<Packet> { - var containsUnknown = false - msgProfileInfos.forEach { modified -> - when (modified.field) { - 20002 -> { // 昵称修改 - val value = modified.value - val to = value.encodeToString() - if (uin == bot.id) { - val from = bot.nick - if (from != to) { - bot.nick = to - bot.asFriend.checkIsFriendImpl().nick = to - add(BotNickChangedEvent(bot, from, to)) - } - } else { - val friend = (bot.getFriend(uin) ?: return@forEach) as FriendImpl - val info = friend.friendInfo - val from = info.nick - when (info) { - is FriendInfoImpl -> info.nick = to - else -> { - bot.network.logger.debug { - "Unknown how to update nick for $info" - } - } - } - add(FriendNickChangedEvent(friend, from, to)) - } - } - else -> { - containsUnknown = true - } - } - } - if (msgProfileInfos.isEmpty() || containsUnknown) { - bot.network.logger.debug { - "Transformers528 0x27L: new data: ${_miraiContentToString()}" - } - } - }.asSequence() - return@lambda528 vProtobuf.loadAs(SubMsgType0x27MsgBody.serializer()).msgModInfos.asSequence() .flatMap { when { - it.msgModFriendRemark != null -> it.msgModFriendRemark.transform(bot) - it.msgDelFriend != null -> it.msgDelFriend.transform(bot) + it.msgModFriendRemark != null -> TODO("removed") + it.msgDelFriend != null -> TODO("removed") it.msgModGroupProfile != null -> it.msgModGroupProfile.transform(bot) it.msgModGroupMemberProfile != null -> it.msgModGroupMemberProfile.transform(bot) - it.msgModCustomFace != null -> it.msgModCustomFace.transform(bot) - it.msgModProfile != null -> it.msgModProfile.transform(bot) + it.msgModCustomFace != null -> TODO("removed") + it.msgModProfile != null -> TODO("removed") else -> { bot.network.logger.debug { "Transformers528 0x27L: new data: ${it._miraiContentToString()}" @@ -856,5 +732,5 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf( } } // 0A 1C 10 28 4A 18 0A 16 08 00 10 A2 FF 8C F0 03 1A 0C E6 BD 9C E6 B1 9F E7 BE A4 E5 8F 8B - } + }, ) diff --git a/mirai-core/src/commonMain/kotlin/utils/AtomicIntSeq.kt b/mirai-core/src/commonMain/kotlin/utils/AtomicIntSeq.kt new file mode 100644 index 000000000..e29aff6b5 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/utils/AtomicIntSeq.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2019-2021 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/master/LICENSE + */ + +package net.mamoe.mirai.internal.utils + +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.update +import net.mamoe.mirai.utils.getRandomUnsignedInt +import net.mamoe.mirai.utils.toLongUnsigned + +// We probably can reduce duplicates by using value classes, but atomicFU compiler might not be able to compile it. + +// TODO: 2021/6/27 tests +internal class AtomicIntSeq private constructor( + initial: Int, + private val maxExclusive: Int, +) { + private val value = atomic(initial) + + /** + * Increment [value] within the range from 0 (inclusive) to [maxExclusive] (exclusive). + */ + fun next(): Int = value.incrementAndGet().mod(maxExclusive) // positive + + /** + * Atomically update [value] if it is smaller than [new]. + */ + fun updateIfSmallerThan(new: Int): Boolean { + value.update { instant -> + if (instant < new) new else return false + } + return true + } + + fun updateIfDifferentWith(new: Int): Boolean { + value.update { instant -> + if (instant == new) return false + new + } + return true + } + + companion object { + @JvmStatic + fun forMessageSeq() = AtomicIntSeq(0, Int.MAX_VALUE) + + @JvmStatic + fun forPrivateSync() = AtomicIntSeq(getRandomUnsignedInt(), 65535) + } +} + +// TODO: 2021/6/27 tests +internal class AtomicLongSeq( + initial: Long = getRandomUnsignedInt().toLongUnsigned(), + private val maxExclusive: Long = 65535, +) { + private val value = atomic(initial) + + /** + * Increment [value] within the range from 0 (inclusive) to [maxExclusive] (exclusive). + */ + fun next(): Long = value.incrementAndGet().mod(maxExclusive) // positive + + /** + * Atomically update [value] if it is smaller than [new]. + */ + fun updateIfSmallerThan(new: Long) { + value.update { instant -> + if (instant < new) new else return + } + } +} \ No newline at end of file