diff --git a/mirai-core-utils/src/commonMain/kotlin/TypeSafeMap.kt b/mirai-core-utils/src/commonMain/kotlin/TypeSafeMap.kt index a214dcc6a..2eec25ba6 100644 --- a/mirai-core-utils/src/commonMain/kotlin/TypeSafeMap.kt +++ b/mirai-core-utils/src/commonMain/kotlin/TypeSafeMap.kt @@ -22,22 +22,86 @@ public value class TypeKey(public val name: String) { public inline infix fun to(value: T): TypeSafeMap = buildTypeSafeMap { set(this@TypeKey, value) } } -@JvmInline -public value class TypeSafeMap( - private val map: MutableMap, Any?> = ConcurrentHashMap() -) { - public operator fun get(key: TypeKey): T = +public interface TypeSafeMap { + public val size: Int + + public operator fun get(key: TypeKey): T + public operator fun contains(key: TypeKey): Boolean = get(key) != null + + public fun toMap(): Map, Any?> + + public companion object { + public val EMPTY: TypeSafeMap = TypeSafeMapImpl(emptyMap()) + } +} + +public operator fun TypeSafeMap.plus(other: TypeSafeMap): TypeSafeMap { + return when { + other.size == 0 -> this + this.size == 0 -> other + else -> buildTypeSafeMap { + setAll(this@plus) + setAll(other) + } + } +} + +public interface MutableTypeSafeMap : TypeSafeMap { + public operator fun set(key: TypeKey, value: T) + public fun remove(key: TypeKey): T? + public fun setAll(other: TypeSafeMap) +} + + +internal open class TypeSafeMapImpl( + internal open val map: Map, Any?> = ConcurrentHashMap() +) : TypeSafeMap { + override val size: Int get() = map.size + + override fun equals(other: Any?): Boolean { + return other is TypeSafeMapImpl && other.map == this.map + } + + override fun hashCode(): Int { + return map.hashCode() + } + + override operator fun get(key: TypeKey): T = map[key]?.uncheckedCast() ?: throw NoSuchElementException(key.toString()) - public operator fun contains(key: TypeKey): Boolean = get(key) != null - public operator fun set(key: TypeKey, value: T) { + override operator fun contains(key: TypeKey): Boolean = get(key) != null + + override fun toMap(): Map, Any?> = map +} + +@PublishedApi +internal class MutableTypeSafeMapImpl( + override val map: MutableMap, Any?> = ConcurrentHashMap() +) : TypeSafeMap, MutableTypeSafeMap, TypeSafeMapImpl(map) { + override fun equals(other: Any?): Boolean { + return other is MutableTypeSafeMapImpl && other.map == this.map + } + + override fun hashCode(): Int { + return map.hashCode() + } + + override operator fun set(key: TypeKey, value: T) { map[key] = value } - public fun remove(key: TypeKey): T? = map.remove(key)?.uncheckedCast() + override fun setAll(other: TypeSafeMap) { + if (other is TypeSafeMapImpl) { + map.putAll(other.map) + } else { + map.putAll(other.toMap()) + } + } + + override fun remove(key: TypeKey): T? = map.remove(key)?.uncheckedCast() } -public inline fun buildTypeSafeMap(block: TypeSafeMap.() -> Unit): TypeSafeMap { +public inline fun buildTypeSafeMap(block: MutableTypeSafeMap.() -> Unit): MutableTypeSafeMap { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return TypeSafeMap().apply(block) + return MutableTypeSafeMapImpl().apply(block) } diff --git a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt index f59b8aafa..b733017eb 100644 --- a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt +++ b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt @@ -45,7 +45,7 @@ import net.mamoe.mirai.internal.network.notice.group.GroupOrMemberListNoticeProc import net.mamoe.mirai.internal.network.notice.group.GroupRecallProcessor import net.mamoe.mirai.internal.network.notice.priv.FriendNoticeProcessor import net.mamoe.mirai.internal.network.notice.priv.OtherClientNoticeProcessor -import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageNoticeProcessor +import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.utils.subLogger import net.mamoe.mirai.utils.BotConfiguration @@ -169,7 +169,7 @@ internal open class QQAndroidBot constructor( FriendNoticeProcessor(pipelineLogger.subLogger("FriendNoticeProcessor")), GroupOrMemberListNoticeProcessor(pipelineLogger.subLogger("GroupOrMemberListNoticeProcessor")), GroupMessageProcessor(pipelineLogger.subLogger("GroupMessageProcessor")), - PrivateMessageNoticeProcessor(), + PrivateMessageProcessor(), OtherClientNoticeProcessor(), UnconsumedNoticesAlerter(pipelineLogger.subLogger("UnconsumedNoticesAlerter")), GroupRecallProcessor() diff --git a/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt b/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt index 0d53dd9aa..6cf81a7cb 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt @@ -15,8 +15,10 @@ import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.ParseErrorPacket import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage +import net.mamoe.mirai.internal.network.notice.BotAware import net.mamoe.mirai.internal.network.notice.decoders.DecodedNotifyMsgBody 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 @@ -27,10 +29,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcP import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushPbPushTransMsg import net.mamoe.mirai.internal.network.toPacket import net.mamoe.mirai.internal.utils.io.ProtocolStruct -import net.mamoe.mirai.utils.TypeKey -import net.mamoe.mirai.utils.TypeSafeMap -import net.mamoe.mirai.utils.toDebugString -import net.mamoe.mirai.utils.uncheckedCast +import net.mamoe.mirai.utils.* import java.util.* import java.util.concurrent.ConcurrentLinkedQueue import kotlin.reflect.KClass @@ -46,7 +45,11 @@ internal interface NoticeProcessorPipeline { /** * Process [data] into [Packet]s. Exceptions are wrapped into [ParseErrorPacket] */ - suspend fun process(bot: QQAndroidBot, data: ProtocolStruct, attributes: TypeSafeMap = TypeSafeMap()): ProcessResult + suspend fun process( + bot: QQAndroidBot, + data: ProtocolStruct, + attributes: TypeSafeMap = TypeSafeMap.EMPTY + ): ProcessResult companion object : ComponentKey { val ComponentStorage.noticeProcessorPipeline get() = get(NoticeProcessorPipeline) @@ -54,7 +57,7 @@ internal interface NoticeProcessorPipeline { @JvmStatic suspend inline fun QQAndroidBot.processPacketThroughPipeline( data: ProtocolStruct, - attributes: TypeSafeMap = TypeSafeMap(), + attributes: TypeSafeMap = TypeSafeMap.EMPTY, ): Packet { return components.noticeProcessorPipeline.process(this, data, attributes).toPacket() } @@ -66,8 +69,8 @@ internal value class MutableProcessResult( val data: MutableCollection ) -internal interface PipelineContext { - val bot: QQAndroidBot +internal interface PipelineContext : BotAware { + override val bot: QQAndroidBot val attributes: TypeSafeMap @@ -83,13 +86,13 @@ internal interface PipelineContext { * and throws a [contextualBugReportException] or logs something. */ @ConsumptionMarker - fun NoticeProcessor.markAsConsumed() + fun NoticeProcessor.markAsConsumed(marker: Any = this) /** * Marks the input as not consumed, if it was marked by this [NoticeProcessor]. */ @ConsumptionMarker - fun NoticeProcessor.markNotConsumed() + fun NoticeProcessor.markNotConsumed(marker: Any = this) @DslMarker annotation class ConsumptionMarker // to give an explicit color. @@ -116,13 +119,21 @@ internal interface PipelineContext { /** * Fire the [data] into the processor pipeline, and collect the results to current [collected]. * + * @param attributes extra attributes * @return result collected from processors. This would also have been collected to this context (where you call [processAlso]). */ - suspend fun processAlso(data: ProtocolStruct): ProcessResult + suspend fun processAlso(data: ProtocolStruct, attributes: TypeSafeMap = TypeSafeMap.EMPTY): ProcessResult companion object { val KEY_FROM_SYNC = TypeKey("fromSync") + val KEY_MSG_INFO = TypeKey("msgInfo") + val PipelineContext.fromSync get() = attributes[KEY_FROM_SYNC] + + /** + * 来自 [MsgInfo] 的数据, 即 [MsgType0x210], [MsgType0x2DC] 的处理过程之中可以使用 + */ + val PipelineContext.msgInfo get() = attributes[KEY_MSG_INFO] } } @@ -142,15 +153,15 @@ internal open class NoticeProcessorPipelineImpl private constructor() : NoticePr inner class ContextImpl( override val bot: QQAndroidBot, override val attributes: TypeSafeMap, ) : PipelineContext { - private val consumers: Stack = Stack() + private val consumers: Stack = Stack() override val isConsumed: Boolean get() = consumers.isNotEmpty() - override fun NoticeProcessor.markAsConsumed() { - consumers.push(this) + override fun NoticeProcessor.markAsConsumed(marker: Any) { + consumers.push(marker) } - override fun NoticeProcessor.markNotConsumed() { - if (consumers.peek() === this) { + override fun NoticeProcessor.markNotConsumed(marker: Any) { + if (consumers.peek() === marker) { consumers.pop() } } @@ -165,8 +176,8 @@ internal open class NoticeProcessorPipelineImpl private constructor() : NoticePr this.collected.data.addAll(packets) } - override suspend fun processAlso(data: ProtocolStruct): ProcessResult { - return process(bot, data, attributes) + override suspend fun processAlso(data: ProtocolStruct, attributes: TypeSafeMap): ProcessResult { + return process(bot, data, this.attributes + attributes) } } diff --git a/mirai-core/src/commonMain/kotlin/network/notice/PrivateContactSupport.kt b/mirai-core/src/commonMain/kotlin/network/notice/PrivateContactSupport.kt new file mode 100644 index 000000000..72125c9cd --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/notice/PrivateContactSupport.kt @@ -0,0 +1,44 @@ +/* + * 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/dev/LICENSE + */ + +package net.mamoe.mirai.internal.network.notice + +import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.contact.GroupImpl + +/////////////////////////////////////////////////////////////////////////// +// Extension interfaces ---- should convert to context receivers in the future. +/////////////////////////////////////////////////////////////////////////// + +internal interface BotAware : PrivateContactSupport { + override val bot: QQAndroidBot +} + +internal interface GroupAware : GroupMemberSupport, BotAware { + override val group: GroupImpl + override val bot: QQAndroidBot get() = group.bot +} + +internal interface PrivateContactSupport { + val bot: QQAndroidBot + + fun Long.findFriend() = bot.friends[this] + fun Long.findStranger() = bot.strangers[this] + fun Long.findFriendOrStranger() = findFriend() ?: findStranger() + fun String.findFriend() = this.toLongOrNull()?.findFriend() + fun String.findStranger() = this.toLongOrNull()?.findStranger() + fun String.findFriendOrStranger() = this.toLongOrNull()?.findFriendOrStranger() +} + +internal interface GroupMemberSupport { + val group: GroupImpl + + fun Long.findMember() = group[this] + fun String.findMember() = this.toLongOrNull()?.findMember() +} \ No newline at end of file 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 f1566844c..2f041d264 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgInfoDecoder.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgInfoDecoder.kt @@ -15,9 +15,11 @@ import kotlinx.io.core.readUInt import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.contact.checkIsGroupImpl import net.mamoe.mirai.internal.network.components.PipelineContext +import net.mamoe.mirai.internal.network.components.PipelineContext.Companion.KEY_MSG_INFO import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController import net.mamoe.mirai.internal.network.components.syncOnlinePush +import net.mamoe.mirai.internal.network.notice.GroupAware 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.SvcReqPushMsg @@ -46,7 +48,7 @@ internal class MsgInfoDecoder( if (!bot.syncController.syncOnlinePush(data)) return when (data.shMsgType.toUShort().toInt()) { // 528 - 0x210 -> processAlso(data.vMsg.loadAs(MsgType0x210.serializer())) + 0x210 -> processAlso(data.vMsg.loadAs(MsgType0x210.serializer()), KEY_MSG_INFO to data) // 732 0x2dc -> { @@ -57,7 +59,7 @@ internal class MsgInfoDecoder( val kind = readByte().toInt() discardExact(1) - processAlso(MsgType0x2DC(kind, group, this.readBytes())) + processAlso(MsgType0x2DC(kind, group, this.readBytes()), KEY_MSG_INFO to data) } } else -> { @@ -67,13 +69,12 @@ internal class MsgInfoDecoder( } } -internal interface BaseMsgType0x2DC { +internal interface BaseMsgType0x2DC : GroupAware { val kind: Int - val group: GroupImpl + override val group: GroupImpl val buf: V - fun Long.findMember() = group[this] - fun String.findMember() = this.toLongOrNull()?.let { group[it] } + override val bot get() = group.bot } internal data class MsgType0x2DC( diff --git a/mirai-core/src/commonMain/kotlin/network/notice/group/GroupMessageProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/group/GroupMessageProcessor.kt index 9bb5bfbac..547d430b1 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/group/GroupMessageProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/group/GroupMessageProcessor.kt @@ -27,7 +27,7 @@ import net.mamoe.mirai.internal.network.components.PipelineContext import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor.MemberNick.Companion.generateMemberNickFromMember -import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageNoticeProcessor +import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush @@ -37,7 +37,7 @@ import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.utils.* /** - * Handles [GroupMessageEvent]. For private message events, see [PrivateMessageNoticeProcessor] + * Handles [GroupMessageEvent]. For private message events, see [PrivateMessageProcessor] */ internal class GroupMessageProcessor( private val logger: MiraiLogger, diff --git a/mirai-core/src/commonMain/kotlin/network/notice/group/GroupNotificationProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/group/GroupNotificationProcessor.kt index 613b38a3d..9ef404884 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/group/GroupNotificationProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/group/GroupNotificationProcessor.kt @@ -16,6 +16,7 @@ import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.QQAndroidBot 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.Packet import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor @@ -23,15 +24,135 @@ import net.mamoe.mirai.internal.network.components.PipelineContext import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.notice.NewContactSupport import net.mamoe.mirai.internal.network.notice.decoders.MsgType0x2DC +import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 +import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x122 +import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27 import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857 import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.serialization.loadAs -import net.mamoe.mirai.utils.context -import net.mamoe.mirai.utils.currentTimeSeconds -import net.mamoe.mirai.utils.debug -import net.mamoe.mirai.utils.read +import net.mamoe.mirai.utils.* internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSupport { + + override suspend fun PipelineContext.processImpl(data: MsgType0x210) = data.context { + when (data.uSubMsgType) { + 0x27L -> { + val body = vProtobuf.loadAs(Submsgtype0x27.SubMsgType0x27.SubMsgType0x27MsgBody.serializer()) + for (msgModInfo in body.msgModInfos) { + markAsConsumed(msgModInfo) + when { + msgModInfo.msgModGroupProfile != null -> handleGroupProfileChanged(msgModInfo.msgModGroupProfile) + msgModInfo.msgModGroupMemberProfile != null -> handleGroupMemberProfileChanged(msgModInfo.msgModGroupMemberProfile) + else -> markNotConsumed(msgModInfo) + } + } + } + } + } + + /** + * @see GroupNameChangeEvent + */ + private fun PipelineContext.handleGroupProfileChanged( + modGroupProfile: Submsgtype0x27.SubMsgType0x27.ModGroupProfile + ) { + for (info in modGroupProfile.msgGroupProfileInfos) { + when (info.field) { + 1 -> { + // 群名 + val new = info.value.encodeToString() + + val group = bot.getGroup(modGroupProfile.groupCode) ?: continue + group.checkIsGroupImpl() + val old = group.name + + if (new == old) continue + + if (modGroupProfile.cmdUin == bot.id) continue + val operator = group[modGroupProfile.cmdUin] ?: continue + + group.settings.nameField = new + + collect(GroupNameChangeEvent(old, new, group, operator)) + } + 2 -> { + // 头像 + // top_package/akkz.java:3446 + /* + var4 = var82.byteAt(0); + short var3 = (short) (var82.byteAt(1) | var4 << 8); + var85 = var18.method_77927(var7 + ""); + var85.troopface = var3; + var85.hasSetNewTroopHead = true; + */ + // bot.logger.debug( + // contextualBugReportException( + // "解析 Transformers528 0x27L ModGroupProfile 群头像修改", + // forDebug = "this=${this._miraiContentToString()}" + // ) + // ) + } + 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()}" + // ) + // ) + } + else -> { + } + } + } + } + + /** + * @see MemberCardChangeEvent + */ + private fun PipelineContext.handleGroupMemberProfileChanged( + modGroupMemberProfile: Submsgtype0x27.SubMsgType0x27.ModGroupMemberProfile + ) { + for (info in modGroupMemberProfile.msgGroupMemberProfileInfos) { + when (info.field) { + 1 -> { // name card + val new = info.value + val group = bot.getGroup(modGroupMemberProfile.groupCode) ?: continue + group.checkIsGroupImpl() + val member = group[modGroupMemberProfile.uin] ?: continue + member.checkIsMemberImpl() + + val old = member.nameCard + + if (new == old) continue + member._nameCard = new + + collect(MemberCardChangeEvent(old, new, member)) + } + 2 -> { + if (info.value.singleOrNull()?.code != 0) { + bot.logger.debug { + "Unknown Transformers528 0x27L ModGroupMemberProfile, field=${info.field}, value=${info.value}" + } + } + continue + } + else -> { + bot.logger.debug { + "Unknown Transformers528 0x27L ModGroupMemberProfile, field=${info.field}, value=${info.value}" + } + continue + } + } + } + } + + + /////////////////////////////////////////////////////////////////////////// + // MsgType0x2DC + /////////////////////////////////////////////////////////////////////////// + override suspend fun PipelineContext.processImpl(data: MsgType0x2DC) { when (data.kind) { 0x0C -> processMute(data) @@ -164,16 +285,17 @@ internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSu * @see NudgeEvent * @see MemberHonorChangeEvent * @see GroupTalkativeChangeEvent - */ + */ // gray tip: 聊天中的灰色小框系统提示信息 private fun PipelineContext.processGrayTip( data: MsgType0x2DC, ) = data.context { val grayTip = buf.loadAs(TroopTips0x857.NotifyMsgBody.serializer(), 1).optGeneralGrayTip markAsConsumed() when (grayTip?.templId) { - // 戳一戳 + // 群戳一戳 10043L, 1133L, 1132L, 1134L, 1135L, 1136L -> { - //预置数据,服务器将不会提供己方已知消息 + // group nudge + // 预置数据,服务器将不会提供己方已知消息 val action = grayTip.msgTemplParam["action_str"].orEmpty() val from = grayTip.msgTemplParam["uin_str1"]?.findMember() ?: group.botAsMember val target = grayTip.msgTemplParam["uin_str2"]?.findMember() ?: group.botAsMember @@ -211,3 +333,7 @@ internal class GroupNotificationProcessor : MixedNoticeProcessor(), NewContactSu } internal operator fun List.get(name: String) = this.findLast { it.name == name }?.value + +@JvmName("get2") +internal operator fun List.get(name: String) = + this.findLast { it.name == name }?.value diff --git a/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt index 2fb02feb2..5695918cb 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt @@ -12,6 +12,9 @@ package net.mamoe.mirai.internal.network.notice.priv import kotlinx.io.core.discardExact import kotlinx.io.core.readUByte import kotlinx.io.core.readUShort +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber +import net.mamoe.mirai.contact.User import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.contact.impl import net.mamoe.mirai.internal.contact.info.FriendInfoImpl @@ -19,17 +22,21 @@ 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.components.PipelineContext.Companion.msgInfo import net.mamoe.mirai.internal.network.notice.NewContactSupport +import net.mamoe.mirai.internal.network.notice.group.get import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.proto.FrdSysMsg import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x115.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.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.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.utils.* @@ -113,10 +120,67 @@ internal class FriendNoticeProcessor( val body = vProtobuf.loadAs(SubMsgType0x115.MsgBody.serializer()) handleInputStatusChanged(body) } + 0x122L -> { + val body = vProtobuf.loadAs(Submsgtype0x122.Submsgtype0x122.MsgBody.serializer()) + when (body.templId) { + //戳一戳 + 1132L, 1133L, 1134L, 1135L, 1136L, 10043L -> handlePrivateNudge(body) + } + } + 0x8AL -> { + val body = vProtobuf.loadAs(Sub8A.serializer()) + processFriendRecall(body) + } else -> markNotConsumed() } } + + @Serializable + private class Wording( + @ProtoNumber(1) val itemID: Int = 0, + @ProtoNumber(2) val itemName: String = "", + ) : ProtoBuf + + @Serializable + private class Sub8AMsgInfo( + @ProtoNumber(1) val fromUin: Long, + @ProtoNumber(2) val botUin: Long, + @ProtoNumber(3) val srcId: Int, + @ProtoNumber(4) val srcInternalId: Long, + @ProtoNumber(5) val time: Long, + @ProtoNumber(6) val random: Int, + @ProtoNumber(7) val pkgNum: Int, // 1 + @ProtoNumber(8) val pkgIndex: Int, // 0 + @ProtoNumber(9) val devSeq: Int, // 0 + @ProtoNumber(12) val flag: Int, // 1 + @ProtoNumber(13) val wording: Wording, + ) : ProtoBuf + + @Serializable + private class Sub8A( + @ProtoNumber(1) val msgInfo: List, + @ProtoNumber(2) val appId: Int, // 1 + @ProtoNumber(3) val instId: Int, // 1 + @ProtoNumber(4) val longMessageFlag: Int, // 0 + @ProtoNumber(5) val reserved: ByteArray? = null, // struct{ boolean(1), boolean(2) } + ) : ProtoBuf + + private fun PipelineContext.processFriendRecall(body: Sub8A) { + for (info in body.msgInfo) { + if (info.botUin != bot.id) continue + collected += MessageRecallEvent.FriendRecall( + bot = bot, + messageIds = intArrayOf(info.srcId), + messageInternalIds = intArrayOf(info.srcInternalId.toInt()), + messageTime = info.time.toInt(), + operatorId = info.fromUin, + operator = bot.getFriend(info.fromUin) ?: continue, + ) + } + } + + private fun PipelineContext.handleInputStatusChanged(body: SubMsgType0x115.MsgBody) { val friend = bot.getFriend(body.fromUin) ?: return val item = body.msgNotifyItem ?: return @@ -203,4 +267,23 @@ internal class FriendNoticeProcessor( collect(FriendAddEvent(added)) if (removed != null) collect(StrangerRelationChangeEvent.Friended(removed, added)) } + + private fun PipelineContext.handlePrivateNudge(body: Submsgtype0x122.Submsgtype0x122.MsgBody) { + val action = body.msgTemplParam["action_str"].orEmpty() + val from = body.msgTemplParam["uin_str1"]?.findFriendOrStranger() ?: bot.asFriend + val target = body.msgTemplParam["uin_str2"]?.findFriendOrStranger() ?: bot.asFriend + val suffix = body.msgTemplParam["suffix_str"].orEmpty() + + val subject: User = bot.getFriend(msgInfo.lFromUin) + ?: bot.getStranger(msgInfo.lFromUin) + ?: return + + collected += NudgeEvent( + from = if (from.id == bot.id) bot else from, + target = if (target.id == bot.id) bot else target, + action = action, + suffix = suffix, + subject = subject, + ) + } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageProcessor.kt similarity index 97% rename from mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageNoticeProcessor.kt rename to mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageProcessor.kt index 00c97a356..943ad1728 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageNoticeProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageProcessor.kt @@ -34,7 +34,7 @@ import net.mamoe.mirai.utils.context * @see GroupTempMessageEvent * @see GroupTempMessageSyncEvent */ -internal class PrivateMessageNoticeProcessor : SimpleNoticeProcessor(type()) { +internal class PrivateMessageProcessor : SimpleNoticeProcessor(type()) { override suspend fun PipelineContext.processImpl(data: MsgComm.Msg) = data.context { markAsConsumed() if (msgHead.fromUin == bot.id && fromSync) { 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 95db783d1..f35d3d29a 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 @@ -408,8 +408,8 @@ internal class Submsgtype0x122 { @Serializable internal class TemplParam( - @ProtoNumber(1) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(2) @JvmField val value: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoNumber(1) @JvmField val name: String = "", + @ProtoNumber(2) @JvmField val value: String = "", ) : ProtoBuf } } 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 528f221c6..56790d6d6 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 @@ -10,36 +10,23 @@ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import kotlinx.io.core.ByteReadPacket -import kotlinx.serialization.Serializable -import kotlinx.serialization.protobuf.ProtoNumber -import net.mamoe.mirai.contact.User -import net.mamoe.mirai.event.events.GroupNameChangeEvent -import net.mamoe.mirai.event.events.MemberCardChangeEvent -import net.mamoe.mirai.event.events.MessageRecallEvent -import net.mamoe.mirai.event.events.NudgeEvent import net.mamoe.mirai.internal.QQAndroidBot 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.MultiPacket import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.processPacketThroughPipeline -import net.mamoe.mirai.internal.network.handler.logger 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.proto.Submsgtype0x122 -import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27.SubMsgType0x27.* 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.utils._miraiContentToString -import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.readUniPacket import net.mamoe.mirai.internal.utils.io.serialization.writeJceRequestPacket import net.mamoe.mirai.utils.debug -import net.mamoe.mirai.utils.encodeToString //0C 01 B1 89 BE 09 5E 3D 72 A6 00 01 73 68 FC 06 00 00 00 3C @@ -206,37 +193,6 @@ internal inline fun lambda528(crossinline block: suspend MsgType0x210.(QQAndroid } } -@Serializable -private class Wording( - @ProtoNumber(1) val itemID: Int = 0, - @ProtoNumber(2) val itemName: String = "", -) : ProtoBuf - -@Serializable -private class Sub8AMsgInfo( - @ProtoNumber(1) val fromUin: Long, - @ProtoNumber(2) val botUin: Long, - @ProtoNumber(3) val srcId: Int, - @ProtoNumber(4) val srcInternalId: Long, - @ProtoNumber(5) val time: Long, - @ProtoNumber(6) val random: Int, - @ProtoNumber(7) val pkgNum: Int, // 1 - @ProtoNumber(8) val pkgIndex: Int, // 0 - @ProtoNumber(9) val devSeq: Int, // 0 - @ProtoNumber(12) val flag: Int, // 1 - @ProtoNumber(13) val wording: Wording, -) : ProtoBuf - -@Serializable -private class Sub8A( - @ProtoNumber(1) val msgInfo: List, - @ProtoNumber(2) val appId: Int, // 1 - @ProtoNumber(3) val instId: Int, // 1 - @ProtoNumber(4) val longMessageFlag: Int, // 0 - @ProtoNumber(5) val reserved: ByteArray? = null, // struct{ boolean(1), boolean(2) } -) : ProtoBuf - - // uSubMsgType to vProtobuf // 138 or 139: top_package/akln.java:1568 // 66: top_package/nhz.java:269 @@ -247,18 +203,7 @@ private class Sub8A( internal object Transformers528 : Map by mapOf( 0x8AL to lambda528 { bot -> - - return@lambda528 vProtobuf.loadAs(Sub8A.serializer()).msgInfo.asSequence() - .filter { it.botUin == bot.id }.mapNotNull { info -> - MessageRecallEvent.FriendRecall( - bot = bot, - messageIds = intArrayOf(info.srcId), - messageInternalIds = intArrayOf(info.srcInternalId.toInt()), - messageTime = info.time.toInt(), - operatorId = info.fromUin, - operator = bot.getFriend(info.fromUin) ?: return@mapNotNull null, - ) - } + TODO("removed") }, //戳一戳信息等 @@ -267,45 +212,7 @@ internal object Transformers528 : Map by mapOf( when (body.templId) { //戳一戳 1132L, 1133L, 1134L, 1135L, 1136L, 10043L -> { - //预置数据,服务器将不会提供己方已知消息 - var from: User? = null - var action = "" - var target: User? = null - var suffix = "" - body.msgTemplParam.asSequence().map { param -> - param.name.decodeToString() to param.value.decodeToString() - }.forEach { (key, value) -> - when (key) { - "action_str" -> action = value - "uin_str1" -> from = bot.getFriend(value.toLong()) ?: bot.getStranger(value.toLong()) - ?: return@lambda528 emptySequence() - "uin_str2" -> target = bot.getFriend(value.toLong()) ?: bot.getStranger(value.toLong()) - ?: return@lambda528 emptySequence() - "suffix_str" -> suffix = value - } - } - - val subject: User = bot.getFriend(msgInfo.lFromUin) - ?: bot.getStranger(msgInfo.lFromUin) - ?: return@lambda528 emptySequence() - - sequenceOf( - when { - target == null && from == null || target?.id == from?.id && from?.id == bot.id -> { - //机器人自己戳自己 - NudgeEvent(from = bot, target = bot, subject = subject, action, suffix) - } - target == null || target!!.id == bot.id -> { - //机器人自身为目标 - NudgeEvent(from = subject, target = bot, subject = subject, action, suffix) - } - from == null || from!!.id == bot.id -> { - //机器人自身为发起者 - NudgeEvent(from = bot, target = subject, subject = subject, action, suffix) - } - else -> NudgeEvent(from = subject, target = subject, subject = subject, action, suffix) - }, - ) + TODO("removed") } else -> { bot.logger.debug { @@ -317,113 +224,6 @@ internal object Transformers528 : Map by mapOf( }, // 群相关, ModFriendRemark, DelFriend, ModGroupProfile 0x27L to lambda528 { bot -> - fun ModGroupProfile.transform(bot: QQAndroidBot): Sequence { - return this.msgGroupProfileInfos.asSequence().mapNotNull { info -> - when (info.field) { - 1 -> { - // 群名 - val new = info.value.encodeToString() - - val group = bot.getGroup(this.groupCode) ?: return@mapNotNull null - group.checkIsGroupImpl() - val old = group.name - - if (new == old) return@mapNotNull null - - val operator = if (this.cmdUin == bot.id) null - else group[this.cmdUin] ?: return@mapNotNull null - - group.settings.nameField = new - - return@mapNotNull GroupNameChangeEvent(old, new, group, operator) - } - 2 -> { - // 头像 - // top_package/akkz.java:3446 - /* - var4 = var82.byteAt(0); - short var3 = (short) (var82.byteAt(1) | var4 << 8); - var85 = var18.method_77927(var7 + ""); - var85.troopface = var3; - var85.hasSetNewTroopHead = true; - */ - // 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()}" - // ) - // ) - null - } - - else -> null - } - } - } - - fun ModGroupMemberProfile.transform(bot: QQAndroidBot): Sequence { - return this.msgGroupMemberProfileInfos.asSequence().mapNotNull { info -> - when (info.field) { - 1 -> { // name card - val new = info.value - val group = bot.getGroup(this.groupCode) ?: return@mapNotNull null - group.checkIsGroupImpl() - val member = group[this.uin] ?: return@mapNotNull null - member.checkIsMemberImpl() - - val old = member.nameCard - - if (new == old) return@mapNotNull null - member._nameCard = new - - return@mapNotNull MemberCardChangeEvent(old, new, member) - } - 2 -> { - if (info.value.singleOrNull()?.code != 0) { - bot.logger.debug { - "Unknown Transformers528 0x27L ModGroupMemberProfile, field=${info.field}, value=${info.value}" - } - } - return@mapNotNull null - } - else -> { - bot.logger.debug { - "Unknown Transformers528 0x27L ModGroupMemberProfile, field=${info.field}, value=${info.value}" - } - return@mapNotNull null - } - } - } - } - - return@lambda528 vProtobuf.loadAs(SubMsgType0x27MsgBody.serializer()).msgModInfos.asSequence() - .flatMap { - when { - 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 -> TODO("removed") - it.msgModProfile != null -> TODO("removed") - else -> { - bot.network.logger.debug { - "Transformers528 0x27L: new data: ${it._miraiContentToString()}" - } - emptySequence() - } - } - } - // 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 + TODO("removed") }, )