Rearrange MessageSvc and OnlinePush

This commit is contained in:
Him188 2020-05-09 16:11:29 +08:00
parent 1c1a37a103
commit 1e885dbf7a
18 changed files with 1526 additions and 1340 deletions

View File

@ -32,7 +32,7 @@ import net.mamoe.mirai.qqandroid.message.firstIsInstanceOrNull
import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.ProfileService
import net.mamoe.mirai.qqandroid.utils.estimateLength
import net.mamoe.mirai.utils.*
@ -358,7 +358,7 @@ internal class GroupImpl(
lateinit var source: MessageSourceToGroupImpl
bot.network.run {
val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.createToGroup(
val response: MessageSvcPbSendMsg.Response = MessageSvcPbSendMsg.createToGroup(
bot.client,
this@GroupImpl,
msg,
@ -366,7 +366,7 @@ internal class GroupImpl(
) {
source = it
}.sendAndExpect()
if (response is MessageSvc.PbSendMsg.Response.Failed) {
if (response is MessageSvcPbSendMsg.Response.Failed) {
when (response.resultType) {
120 -> throw BotIsBeingMutedException(this@GroupImpl)
34 -> {

View File

@ -32,7 +32,7 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.message.MessageSourceToTempImpl
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopMemberInfo
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
import net.mamoe.mirai.utils.*
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@ -68,13 +68,13 @@ internal class MemberImpl constructor(
lateinit var source: MessageSourceToTempImpl
bot.network.run {
check(
MessageSvc.PbSendMsg.createToTemp(
MessageSvcPbSendMsg.createToTemp(
bot.client,
this@MemberImpl,
message.asMessageChain()
) {
source = it
}.sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
}.sendAndExpect<MessageSvcPbSendMsg.Response>() is MessageSvcPbSendMsg.Response.SUCCESS
) { "send message failed" }
}
return MessageReceipt(source, this, null)

View File

@ -23,7 +23,7 @@ import net.mamoe.mirai.qqandroid.message.MessageSourceToFriendImpl
import net.mamoe.mirai.qqandroid.message.ensureSequenceIdAvailable
import net.mamoe.mirai.qqandroid.message.firstIsInstanceOrNull
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.verbose
@ -38,13 +38,13 @@ internal suspend fun <T : Contact> Friend.sendMessageImpl(generic: T, message: M
lateinit var source: MessageSourceToFriendImpl
(bot.network as QQAndroidBotNetworkHandler).run {
check(
MessageSvc.PbSendMsg.createToFriend(
MessageSvcPbSendMsg.createToFriend(
bot.asQQAndroidBot().client,
this@sendMessageImpl,
event.message
) {
source = it
}.sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
}.sendAndExpect<MessageSvcPbSendMsg.Response>() is MessageSvcPbSendMsg.Response.SUCCESS
) { "send message failed" }
}
return MessageReceipt(source, generic, null)

View File

@ -25,7 +25,7 @@ import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePushPbPushGroupMsg.SendGroupMessageReceipt
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
@ -117,7 +117,7 @@ internal class MessageSourceToGroupImpl(
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
private val sequenceIdDeferred: Deferred<Int?> =
coroutineScope.asyncFromEventOrNull<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(
coroutineScope.asyncFromEventOrNull<SendGroupMessageReceipt, Int>(
timeoutMillis = 3000
) {
if (it.messageRandom == this@MessageSourceToGroupImpl.internalId) {

View File

@ -32,7 +32,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopNum
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvcPbGetMsg
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.ConfigPushSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat
@ -362,8 +362,8 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
logger.info { "Syncing friend message history..." }
withTimeoutOrNull(30000) {
launch { syncFromEvent<MessageSvc.PbGetMsg.GetMsgSuccess, Unit> { Unit } }
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect<Packet>()
launch { syncFromEvent<MessageSvcPbGetMsg.GetMsgSuccess, Unit> { Unit } }
MessageSvcPbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect<Packet>()
} ?: error("timeout syncing friend message history")
logger.info { "Syncing friend message history: Success" }

View File

@ -897,7 +897,7 @@ internal class Submsgtype0x27 {
) : ProtoBuf
@Serializable
internal class MsgBody(
internal class SubMsgType0x27MsgBody(
@ProtoId(1) @JvmField val msgModInfos: List<ForwardBody> = listOf()
) : ProtoBuf

View File

@ -19,8 +19,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.ProfileService
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.ConfigPushSvc
@ -132,10 +131,10 @@ internal object KnownPacketFactories {
WtLogin.Login,
StatSvc.Register,
StatSvc.GetOnlineStatus,
MessageSvc.PbGetMsg,
MessageSvc.PushForceOffline,
MessageSvc.PbSendMsg,
MessageSvc.Del,
MessageSvcPbGetMsg,
MessageSvcPushForceOffline,
MessageSvcPbSendMsg,
MessageSvcPbDeleteMsg,
FriendList.GetFriendGroupList,
FriendList.GetTroopListSimplify,
FriendList.GetTroopMemberList,
@ -157,15 +156,15 @@ internal object KnownPacketFactories {
)
object IncomingFactories : List<IncomingPacketFactory<*>> by mutableListOf(
OnlinePush.PbPushGroupMsg,
OnlinePush.ReqPush,
OnlinePush.PbPushTransMsg,
MessageSvc.PushNotify,
OnlinePushPbPushGroupMsg,
OnlinePushReqPush,
OnlinePushPbPushTransMsg,
MessageSvcPushNotify,
ConfigPushSvc.PushReq,
StatSvc.ReqMSFOffline
)
// SvcReqMSFLoginNotify 自己的其他设备上限
// MessageSvc.PushReaded 电脑阅读了别人的消息, 告知手机
// MessageSvcPushReaded 电脑阅读了别人的消息, 告知手机
// OnlinePush.PbC2CMsgSync 电脑发消息给别人, 同步给手机
@Suppress("MemberVisibilityCanBePrivate") // debugging use
@ -259,21 +258,20 @@ internal object KnownPacketFactories {
PacketLogger.info { "Handle packet: ${it.commandName}" }
it.data.withUse {
when (flag2) {
0, 1 ->
when (it.packetFactory) {
is OutgoingPacketFactory<*> -> consumer(
it.packetFactory as OutgoingPacketFactory<T>,
it.packetFactory.run { decode(bot, it.data) },
it.packetFactory.commandName,
it.sequenceId
)
is IncomingPacketFactory<*> -> consumer(
it.packetFactory as IncomingPacketFactory<T>,
it.packetFactory.run { decode(bot, it.data, it.sequenceId) },
it.packetFactory.receivingCommandName,
it.sequenceId
)
}
0, 1 -> when (it.packetFactory) {
is OutgoingPacketFactory<*> -> consumer(
it.packetFactory as OutgoingPacketFactory<T>,
it.packetFactory.run { decode(bot, it.data) },
it.packetFactory.commandName,
it.sequenceId
)
is IncomingPacketFactory<*> -> consumer(
it.packetFactory as IncomingPacketFactory<T>,
it.packetFactory.run { decode(bot, it.data, it.sequenceId) },
it.packetFactory.receivingCommandName,
it.sequenceId
)
}
2 -> it.data.parseOicqResponse(
bot,

View File

@ -0,0 +1,55 @@
/*
* Copyright 2020 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.qqandroid.network.protocol.packet.chat.receive
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
internal object MessageSvcPbDeleteMsg : OutgoingPacketFactory<Nothing?>("MessageSvcPbDeleteMsg") {
internal operator fun invoke(client: QQAndroidClient, items: List<MsgSvc.PbDeleteMsgReq.MsgItem>) =
buildOutgoingUniPacket(client) {
writeProtoBuf(
MsgSvc.PbDeleteMsgReq.serializer(),
MsgSvc.PbDeleteMsgReq(
msgItems = items
)
)
}
internal suspend fun delete(bot: QQAndroidBot, messages: Flow<MsgComm.Msg>) =
bot.network.run {
val map = messages.map {
MsgSvc.PbDeleteMsgReq.MsgItem(
fromUin = it.msgHead.fromUin,
toUin = it.msgHead.toUin,
// 群为84、好友为187。群通过其他方法删除但测试结果显示通过187也能删除群消息。
msgType = 187,
msgSeq = it.msgHead.msgSeq,
msgUid = it.msgHead.msgUid
)
}.toList()
MessageSvcPbDeleteMsg(bot.client, map).sendWithoutExpect()
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot) = null
}

View File

@ -0,0 +1,346 @@
/*
* Copyright 2020 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.qqandroid.network.protocol.packet.chat.receive
import kotlinx.atomicfu.loop
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.*
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.events.BotJoinGroupEvent
import net.mamoe.mirai.event.events.MemberJoinEvent
import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.message.FriendMessageEvent
import net.mamoe.mirai.message.TempMessageEvent
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.GroupImpl
import net.mamoe.mirai.qqandroid.contact.checkIsFriendImpl
import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl
import net.mamoe.mirai.qqandroid.message.toMessageChain
import net.mamoe.mirai.qqandroid.network.MultiPacket
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.NewContact
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
import net.mamoe.mirai.utils.*
/**
* 获取好友消息和消息记录
*/
@OptIn(MiraiInternalAPI::class)
internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Response>("MessageSvcPbGetMsg") {
@Suppress("SpellCheckingInspection")
operator fun invoke(
client: QQAndroidClient,
syncFlag: MsgSvc.SyncFlag = MsgSvc.SyncFlag.START,
msgTime: Long //PbPushMsg.msg.msgHead.msgTime
): OutgoingPacket = buildOutgoingUniPacket(
client
) {
//println("syncCookie=${client.c2cMessageSync.syncCookie?.toUHexString()}")
writeProtoBuf(
MsgSvc.PbGetMsgReq.serializer(),
MsgSvc.PbGetMsgReq(
msgReqType = 1, // from.ctype.toInt()
contextFlag = 1,
rambleFlag = 0,
latestRambleNumber = 20,
otherRambleNumber = 3,
onlineSyncFlag = 1,
whisperSessionId = 0,
syncFlag = syncFlag,
// serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY,
syncCookie = client.c2cMessageSync.syncCookie
?: SyncCookie(time = msgTime).toByteArray(SyncCookie.serializer())//.also { client.c2cMessageSync.syncCookie = it },
// syncFlag = client.c2cMessageSync.syncFlag,
//msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf,
//pubaccountCookie = client.c2cMessageSync.pubAccountCookie
)
)
}
@OptIn(MiraiInternalAPI::class)
open class GetMsgSuccess(delegate: List<Packet>) : Response(MsgSvc.SyncFlag.STOP, delegate), Event,
Packet.NoLog {
override fun toString(): String = "MessageSvcPbGetMsg.GetMsgSuccess(messages=<Iterable>))"
}
/**
* 不要直接 expect 这个 class. 它可能还没同步完成
*/
@MiraiInternalAPI
open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: List<Packet>) :
AbstractEvent(),
MultiPacket<Packet>,
Iterable<Packet> by (delegate) {
override fun toString(): String =
"MessageSvcPbGetMsg.Response(syncFlagFromServer=$syncFlagFromServer, messages=<Iterable>))"
}
object EmptyResponse : GetMsgSuccess(emptyList())
private suspend fun MsgComm.Msg.getNewGroup(bot: QQAndroidBot): Group? {
val troopNum = bot.network.run {
FriendList.GetTroopListSimplify(bot.client)
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
}.groups.firstOrNull { it.groupUin == msgHead.fromUin } ?: return null
@Suppress("DuplicatedCode")
return GroupImpl(
bot = bot,
coroutineContext = bot.coroutineContext,
id = Group.calculateGroupCodeByGroupUin(msgHead.fromUin),
groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply {
this as GroupInfoImpl
if (this.delegate.groupName == null) {
this.delegate.groupName = troopNum.groupName
}
if (this.delegate.groupMemo == null) {
this.delegate.groupMemo = troopNum.groupMemo
}
if (this.delegate.groupUin == null) {
this.delegate.groupUin = troopNum.groupUin
}
this.delegate.groupCode = troopNum.groupCode
},
members = bot._lowLevelQueryGroupMemberList(
troopNum.groupUin,
troopNum.groupCode,
troopNum.dwGroupOwnerUin
)
)
}
@OptIn(LowLevelAPI::class)
private fun MsgComm.Msg.getNewMemberInfo(): MemberInfo {
return object : MemberInfo {
override val nameCard: String get() = ""
override val permission: MemberPermission get() = MemberPermission.MEMBER
override val specialTitle: String get() = ""
override val muteTimestamp: Int get() = 0
override val uin: Long get() = msgHead.authUin
override val nick: String = msgHead.authNick.takeIf { it.isNotEmpty() }
?: msgHead.fromNick
}
}
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class, FlowPreview::class, LowLevelAPI::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
// 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00
val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer())
if (resp.result != 0) {
bot.network.logger
.warning { "MessageSvcPushNotify: result != 0, result = ${resp.result}, errorMsg=${resp.errmsg}" }
return EmptyResponse
}
bot.client.c2cMessageSync.syncCookie = resp.syncCookie
bot.client.c2cMessageSync.pubAccountCookie = resp.pubAccountCookie
bot.client.c2cMessageSync.msgCtrlBuf = resp.msgCtrlBuf
if (resp.uinPairMsgs == null) {
return EmptyResponse
}
val messages = resp.uinPairMsgs.asFlow()
.filterNot { it.msg == null }
.flatMapConcat { it.msg!!.asFlow() }
.also {
MessageSvcPbDeleteMsg.delete(
bot,
it)
} // 删除消息
.mapNotNull<MsgComm.Msg, Packet> { msg ->
when (msg.msgHead.msgType) {
33 -> { // 邀请入群
val group = bot.getGroupByUinOrNull(msg.msgHead.fromUin)
if (msg.msgHead.authUin == bot.id) {
if (group != null) {
return@mapNotNull null
}
// 新群
val newGroup = msg.getNewGroup(bot) ?: return@mapNotNull null
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
bot.groups.delegate.addLast(newGroup)
return@mapNotNull BotJoinGroupEvent(newGroup)
} else {
group ?: return@mapNotNull null
if (group.members.contains(msg.msgHead.authUin)) {
return@mapNotNull null
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo())
.also { group.members.delegate.addLast(it) })
}
}
34 -> { // 主动入群
// 27 0B 60 E7 01 44 71 47 90 03 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 36 42 35 35 46 45 32 45 35 36 43 45 45 44 30 38 30 35 31 41 35 42 37 36 39 35 34 45 30 46 43 43 36 36 45 44 43 46 45 43 42 39 33 41 41 44 32 32
val group = bot.getGroupByUinOrNull(msg.msgHead.fromUin)
group ?: return@mapNotNull null
if (group.members.contains(msg.msgHead.authUin)) {
return@mapNotNull null
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo())
.also { group.members.delegate.addLast(it) })
}
166 -> {
if (msg.msgHead.fromUin == bot.id) {
loop@ while (true) {
val instance = bot.client.getFriendSeq()
if (instance < msg.msgHead.msgSeq) {
if (bot.client.setFriendSeq(instance, msg.msgHead.msgSeq)) {
break@loop
}
} else break@loop
}
return@mapNotNull null
}
val friend = bot.getFriendOrNull(msg.msgHead.fromUin) ?: return@mapNotNull null
friend.checkIsFriendImpl()
if (!bot.firstLoginSucceed) {
return@mapNotNull null
}
friend.lastMessageSequence.loop { instant ->
if (msg.msgHead.msgSeq > instant) {
if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
return@mapNotNull FriendMessageEvent(
friend,
msg.toMessageChain(bot, groupIdOrZero = 0, onlineSource = true),
msg.msgHead.msgTime
)
}
} else return@mapNotNull null
}
}
208 -> {
// friend ptt
return@mapNotNull null
}
529 -> {
// 好友文件
return@mapNotNull null
}
141 -> {
val tmpHead = msg.msgHead.c2cTmpMsgHead ?: return@mapNotNull null
val member = bot.getGroupByUinOrNull(tmpHead.groupUin)?.getOrNull(msg.msgHead.fromUin)
?: return@mapNotNull null
member.checkIsMemberImpl()
if (msg.msgHead.fromUin == bot.id || !bot.firstLoginSucceed) {
return@mapNotNull null
}
member.lastMessageSequence.loop { instant ->
if (msg.msgHead.msgSeq > instant) {
if (member.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
return@mapNotNull TempMessageEvent(
member,
msg.toMessageChain(
bot,
groupIdOrZero = 0,
onlineSource = true,
isTemp = true
),
msg.msgHead.msgTime
)
}
} else return@mapNotNull null
}
}
84, 87 -> { // 请求入群验证 和 被要求入群
bot.network.run {
NewContact.SystemMsgNewGroup(bot.client).sendWithoutExpect()
}
return@mapNotNull null
}
187 -> { // 请求加好友验证
bot.network.run {
NewContact.SystemMsgNewFriend(bot.client).sendWithoutExpect()
}
return@mapNotNull null
}
// 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
else -> {
bot.network.logger.debug { "unknown PbGetMsg type ${msg.msgHead.msgType}" }
return@mapNotNull null
}
}
}
val list: List<Packet> = messages.toList()
if (resp.syncFlag == MsgSvc.SyncFlag.STOP) {
return GetMsgSuccess(
list)
}
return Response(
resp.syncFlag,
list)
}
override suspend fun QQAndroidBot.handle(packet: Response) {
when (packet.syncFlagFromServer) {
MsgSvc.SyncFlag.STOP -> return
MsgSvc.SyncFlag.START -> {
network.run {
MessageSvcPbGetMsg(
client,
MsgSvc.SyncFlag.CONTINUE,
currentTimeSeconds).sendAndExpect<Packet>()
}
return
}
MsgSvc.SyncFlag.CONTINUE -> {
network.run {
MessageSvcPbGetMsg(
client,
MsgSvc.SyncFlag.CONTINUE,
currentTimeSeconds).sendAndExpect<Packet>()
}
return
}
}
}
}

View File

@ -0,0 +1,237 @@
/*
* Copyright 2020 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.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PttMessage
import net.mamoe.mirai.message.data.firstOrNull
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.GroupImpl
import net.mamoe.mirai.qqandroid.message.MessageSourceToFriendImpl
import net.mamoe.mirai.qqandroid.message.MessageSourceToGroupImpl
import net.mamoe.mirai.qqandroid.message.MessageSourceToTempImpl
import net.mamoe.mirai.qqandroid.message.toRichTextElems
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
import net.mamoe.mirai.utils.currentTimeSeconds
import kotlin.math.absoluteValue
import kotlin.random.Random
internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.Response>("MessageSvcPbSendMsg") {
sealed class Response : Packet {
object SUCCESS : Response() {
override fun toString(): String = "MessageSvcPbSendMsg.Response.SUCCESS"
}
/**
* 121: 被限制? 个别号才不能发
*/
data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() {
override fun toString(): String =
"MessageSvcPbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)"
}
}
inline fun createToFriend(
client: QQAndroidClient,
qq: Friend,
message: MessageChain,
crossinline sourceCallback: (MessageSourceToFriendImpl) -> Unit
): OutgoingPacket {
val rand = Random.nextInt().absoluteValue
val source = MessageSourceToFriendImpl(
internalId = rand,
sender = client.bot,
target = qq,
time = currentTimeSeconds.toInt(),
sequenceId = client.nextFriendSeq(),
originalMessage = message
)
sourceCallback(source)
return createToFriend(
client,
qq.id,
message,
source)
}
/**
* 发送好友消息
*/
@Suppress("FunctionName")
private fun createToFriend(
client: QQAndroidClient,
toUin: Long,
message: MessageChain,
source: MessageSourceToFriendImpl
): OutgoingPacket = buildOutgoingUniPacket(client) {
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
///return@buildOutgoingUniPacket
writeProtoBuf(
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = toUin)),
contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = message.toRichTextElems(forGroup = false, withGeneralFlags = true)
)
),
msgSeq = source.sequenceId,
msgRand = source.internalId,
syncCookie = SyncCookie(time = source.time.toLong()).toByteArray(SyncCookie.serializer())
// msgVia = 1
)
)
}
inline fun createToTemp(
client: QQAndroidClient,
member: Member,
message: MessageChain,
sourceCallback: (MessageSourceToTempImpl) -> Unit
): OutgoingPacket {
val source = MessageSourceToTempImpl(
internalId = Random.nextInt().absoluteValue,
sender = client.bot,
target = member,
time = currentTimeSeconds.toInt(),
sequenceId = client.atomicNextMessageSequenceId(),
originalMessage = message
)
sourceCallback(source)
return createToTemp(
client,
(member.group as GroupImpl).uin,
member.id,
message,
source)
}
/**
* 发送临时消息
*/
private fun createToTemp(
client: QQAndroidClient,
groupUin: Long,
toUin: Long,
message: MessageChain,
source: MessageSourceToTempImpl
): OutgoingPacket = buildOutgoingUniPacket(client) {
writeProtoBuf(
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
routingHead = MsgSvc.RoutingHead(
grpTmp = MsgSvc.GrpTmp(groupUin, toUin)
),
contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = message.toRichTextElems(forGroup = false, withGeneralFlags = true)
)
),
msgSeq = source.sequenceId,
msgRand = source.internalId,
syncCookie = SyncCookie(time = source.time.toLong()).toByteArray(SyncCookie.serializer())
)
)
}
inline fun createToGroup(
client: QQAndroidClient,
group: Group,
message: MessageChain,
isForward: Boolean,
sourceCallback: (MessageSourceToGroupImpl) -> Unit
): OutgoingPacket {
val source = MessageSourceToGroupImpl(
group,
internalId = Random.nextInt().absoluteValue,
sender = client.bot,
target = group,
time = currentTimeSeconds.toInt(),
originalMessage = message//,
// sourceMessage = message
)
sourceCallback(source)
return createToGroup(
client,
group.id,
message,
isForward,
source)
}
/**
* 发送群消息
*/
@Suppress("FunctionName")
private fun createToGroup(
client: QQAndroidClient,
groupCode: Long,
message: MessageChain,
isForward: Boolean,
source: MessageSourceToGroupImpl
): OutgoingPacket = buildOutgoingUniPacket(client) {
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
// DebugLogger.debug("sending group message: " + message.toRichTextElems().contentToString())
///return@buildOutgoingUniPacket
writeProtoBuf(
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
routingHead = MsgSvc.RoutingHead(grp = MsgSvc.Grp(groupCode = groupCode)),
contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = message.toRichTextElems(forGroup = true, withGeneralFlags = true),
ptt = message.firstOrNull(PttMessage)?.run {
ImMsgBody.Ptt(fileName = fileName.toByteArray(), fileMd5 = md5)
}
)
),
msgSeq = client.atomicNextMessageSequenceId(),
msgRand = source.internalId,
syncCookie = EMPTY_BYTE_ARRAY,
msgVia = 1,
msgCtrl = if (isForward) MsgCtrl.MsgCtrl(
msgFlag = 4
) else null
)
)
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
val response = readProtoBuf(MsgSvc.PbSendMsgResp.serializer())
return if (response.result == 0) {
Response.SUCCESS
} else {
Response.Failed(
response.result,
response.errtype,
response.errmsg)
}
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2020 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.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.utils.io.serialization.readUniPacket
/**
* 被挤下线
*/
internal object MessageSvcPushForceOffline :
OutgoingPacketFactory<BotOfflineEvent.Force>("MessageSvcPushForceOffline") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): BotOfflineEvent.Force {
val struct = this.readUniPacket(RequestPushForceOffline.serializer())
return BotOfflineEvent.Force(bot, title = struct.title ?: "", message = struct.tips ?: "")
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2020 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.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.utils.io.serialization.readUniPacket
import net.mamoe.mirai.utils.currentTimeSeconds
/**
* 告知要刷新好友消息
*/
internal object MessageSvcPushNotify : IncomingPacketFactory<RequestPushNotify>("MessageSvcPushNotify") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): RequestPushNotify {
discardExact(4) // don't remove
return readUniPacket(RequestPushNotify.serializer())
}
override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket? {
network.run {
return MessageSvcPbGetMsg(
client,
MsgSvc.SyncFlag.START,
packet.stMsgInfo?.uMsgTime ?: currentTimeSeconds
)
}
}
}

View File

@ -1,600 +0,0 @@
/*
* Copyright 2020 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
*/
@file: OptIn(LowLevelAPI::class)
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.atomicfu.loop
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.*
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.events.BotJoinGroupEvent
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.MemberJoinEvent
import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.message.FriendMessageEvent
import net.mamoe.mirai.message.TempMessageEvent
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PttMessage
import net.mamoe.mirai.message.data.Voice
import net.mamoe.mirai.message.data.firstOrNull
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.GroupImpl
import net.mamoe.mirai.qqandroid.contact.checkIsFriendImpl
import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl
import net.mamoe.mirai.qqandroid.message.*
import net.mamoe.mirai.qqandroid.network.MultiPacket
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgCtrl.MsgCtrl
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.NewContact
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.qqandroid.utils._miraiContentToString
import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.utils.io.serialization.readUniPacket
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
import net.mamoe.mirai.utils.*
import kotlin.collections.firstOrNull
import kotlin.math.absoluteValue
import kotlin.random.Random
internal class MessageSvc {
/**
* 告知要刷新好友消息
*/
internal object PushNotify : IncomingPacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): RequestPushNotify {
discardExact(4) // don't remove
return readUniPacket(RequestPushNotify.serializer())
}
override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket? {
network.run {
return PbGetMsg(
client,
MsgSvc.SyncFlag.START,
packet.stMsgInfo?.uMsgTime ?: currentTimeSeconds
)
}
}
}
/**
* 获取好友消息和消息记录
*/
@OptIn(MiraiInternalAPI::class)
internal object PbGetMsg : OutgoingPacketFactory<PbGetMsg.Response>("MessageSvc.PbGetMsg") {
@Suppress("SpellCheckingInspection")
operator fun invoke(
client: QQAndroidClient,
syncFlag: MsgSvc.SyncFlag = MsgSvc.SyncFlag.START,
msgTime: Long //PbPushMsg.msg.msgHead.msgTime
): OutgoingPacket = buildOutgoingUniPacket(
client
) {
//println("syncCookie=${client.c2cMessageSync.syncCookie?.toUHexString()}")
writeProtoBuf(
MsgSvc.PbGetMsgReq.serializer(),
MsgSvc.PbGetMsgReq(
msgReqType = 1, // from.ctype.toInt()
contextFlag = 1,
rambleFlag = 0,
latestRambleNumber = 20,
otherRambleNumber = 3,
onlineSyncFlag = 1,
whisperSessionId = 0,
syncFlag = syncFlag,
// serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY,
syncCookie = client.c2cMessageSync.syncCookie
?: SyncCookie(time = msgTime).toByteArray(SyncCookie.serializer())//.also { client.c2cMessageSync.syncCookie = it },
// syncFlag = client.c2cMessageSync.syncFlag,
//msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf,
//pubaccountCookie = client.c2cMessageSync.pubAccountCookie
)
)
}
@OptIn(MiraiInternalAPI::class)
open class GetMsgSuccess(delegate: List<Packet>) : Response(MsgSvc.SyncFlag.STOP, delegate), Event,
Packet.NoLog {
override fun toString(): String = "MessageSvc.PbGetMsg.GetMsgSuccess(messages=<Iterable>))"
}
/**
* 不要直接 expect 这个 class. 它可能还没同步完成
*/
@MiraiInternalAPI
open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: List<Packet>) :
AbstractEvent(),
MultiPacket<Packet>,
Iterable<Packet> by (delegate) {
override fun toString(): String =
"MessageSvc.PbGetMsg.Response(syncFlagFromServer=$syncFlagFromServer, messages=<Iterable>))"
}
object EmptyResponse : GetMsgSuccess(emptyList())
private suspend fun MsgComm.Msg.getNewGroup(bot: QQAndroidBot): Group? {
val troopNum = bot.network.run {
FriendList.GetTroopListSimplify(bot.client)
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
}.groups.firstOrNull { it.groupUin == msgHead.fromUin } ?: return null
@Suppress("DuplicatedCode")
return GroupImpl(
bot = bot,
coroutineContext = bot.coroutineContext,
id = Group.calculateGroupCodeByGroupUin(msgHead.fromUin),
groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply {
this as GroupInfoImpl
if (this.delegate.groupName == null) {
this.delegate.groupName = troopNum.groupName
}
if (this.delegate.groupMemo == null) {
this.delegate.groupMemo = troopNum.groupMemo
}
if (this.delegate.groupUin == null) {
this.delegate.groupUin = troopNum.groupUin
}
this.delegate.groupCode = troopNum.groupCode
},
members = bot._lowLevelQueryGroupMemberList(
troopNum.groupUin,
troopNum.groupCode,
troopNum.dwGroupOwnerUin
)
)
}
private fun MsgComm.Msg.getNewMemberInfo(): MemberInfo {
return object : MemberInfo {
override val nameCard: String get() = ""
override val permission: MemberPermission get() = MemberPermission.MEMBER
override val specialTitle: String get() = ""
override val muteTimestamp: Int get() = 0
override val uin: Long get() = msgHead.authUin
override val nick: String = msgHead.authNick.takeIf { it.isNotEmpty() }
?: msgHead.fromNick
}
}
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class, FlowPreview::class, LowLevelAPI::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
// 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00
val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer())
if (resp.result != 0) {
bot.network.logger
.warning { "MessageSvc.PushNotify: result != 0, result = ${resp.result}, errorMsg=${resp.errmsg}" }
return EmptyResponse
}
bot.client.c2cMessageSync.syncCookie = resp.syncCookie
bot.client.c2cMessageSync.pubAccountCookie = resp.pubAccountCookie
bot.client.c2cMessageSync.msgCtrlBuf = resp.msgCtrlBuf
if (resp.uinPairMsgs == null) {
return EmptyResponse
}
val messages = resp.uinPairMsgs.asFlow()
.filterNot { it.msg == null }
.flatMapConcat { it.msg!!.asFlow() }
.also { Del.delete(bot, it) } // 删除消息
.mapNotNull<MsgComm.Msg, Packet> { msg ->
when (msg.msgHead.msgType) {
33 -> { // 邀请入群
val group = bot.getGroupByUinOrNull(msg.msgHead.fromUin)
if (msg.msgHead.authUin == bot.id) {
if (group != null) {
return@mapNotNull null
}
// 新群
val newGroup = msg.getNewGroup(bot) ?: return@mapNotNull null
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
bot.groups.delegate.addLast(newGroup)
return@mapNotNull BotJoinGroupEvent(newGroup)
} else {
group ?: return@mapNotNull null
if (group.members.contains(msg.msgHead.authUin)) {
return@mapNotNull null
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo())
.also { group.members.delegate.addLast(it) })
}
}
34 -> { // 主动入群
// 27 0B 60 E7 01 44 71 47 90 03 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 36 42 35 35 46 45 32 45 35 36 43 45 45 44 30 38 30 35 31 41 35 42 37 36 39 35 34 45 30 46 43 43 36 36 45 44 43 46 45 43 42 39 33 41 41 44 32 32
val group = bot.getGroupByUinOrNull(msg.msgHead.fromUin)
group ?: return@mapNotNull null
if (group.members.contains(msg.msgHead.authUin)) {
return@mapNotNull null
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo())
.also { group.members.delegate.addLast(it) })
}
166 -> {
if (msg.msgHead.fromUin == bot.id) {
loop@ while (true) {
val instance = bot.client.getFriendSeq()
if (instance < msg.msgHead.msgSeq) {
if (bot.client.setFriendSeq(instance, msg.msgHead.msgSeq)) {
break@loop
}
} else break@loop
}
return@mapNotNull null
}
val friend = bot.getFriendOrNull(msg.msgHead.fromUin) ?: return@mapNotNull null
friend.checkIsFriendImpl()
if (!bot.firstLoginSucceed) {
return@mapNotNull null
}
friend.lastMessageSequence.loop { instant ->
if (msg.msgHead.msgSeq > instant) {
if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
return@mapNotNull FriendMessageEvent(
friend,
msg.toMessageChain(bot, groupIdOrZero = 0, onlineSource = true),
msg.msgHead.msgTime
)
}
} else return@mapNotNull null
}
}
208 -> {
// friend ptt
return@mapNotNull null
}
529 -> {
// 好友文件
return@mapNotNull null
}
141 -> {
val tmpHead = msg.msgHead.c2cTmpMsgHead ?: return@mapNotNull null
val member = bot.getGroupByUinOrNull(tmpHead.groupUin)?.getOrNull(msg.msgHead.fromUin)
?: return@mapNotNull null
member.checkIsMemberImpl()
if (msg.msgHead.fromUin == bot.id || !bot.firstLoginSucceed) {
return@mapNotNull null
}
member.lastMessageSequence.loop { instant ->
if (msg.msgHead.msgSeq > instant) {
if (member.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
return@mapNotNull TempMessageEvent(
member,
msg.toMessageChain(
bot,
groupIdOrZero = 0,
onlineSource = true,
isTemp = true
),
msg.msgHead.msgTime
)
}
} else return@mapNotNull null
}
}
84, 87 -> { // 请求入群验证 和 被要求入群
bot.network.run {
NewContact.SystemMsgNewGroup(bot.client).sendWithoutExpect()
}
return@mapNotNull null
}
187 -> { // 请求加好友验证
bot.network.run {
NewContact.SystemMsgNewFriend(bot.client).sendWithoutExpect()
}
return@mapNotNull null
}
// 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
else -> {
bot.network.logger.debug { "unknown PbGetMsg type ${msg.msgHead.msgType}" }
return@mapNotNull null
}
}
}
val list: List<Packet> = messages.toList()
if (resp.syncFlag == MsgSvc.SyncFlag.STOP) {
return GetMsgSuccess(list)
}
return Response(resp.syncFlag, list)
}
override suspend fun QQAndroidBot.handle(packet: Response) {
when (packet.syncFlagFromServer) {
MsgSvc.SyncFlag.STOP -> return
MsgSvc.SyncFlag.START -> {
network.run {
PbGetMsg(client, MsgSvc.SyncFlag.CONTINUE, currentTimeSeconds).sendAndExpect<Packet>()
}
return
}
MsgSvc.SyncFlag.CONTINUE -> {
network.run {
PbGetMsg(client, MsgSvc.SyncFlag.CONTINUE, currentTimeSeconds).sendAndExpect<Packet>()
}
return
}
}
}
}
/**
* 被挤下线
*/
internal object PushForceOffline : OutgoingPacketFactory<BotOfflineEvent.Force>("MessageSvc.PushForceOffline") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): BotOfflineEvent.Force {
val struct = this.readUniPacket(RequestPushForceOffline.serializer())
return BotOfflineEvent.Force(bot, title = struct.title ?: "", message = struct.tips ?: "")
}
}
internal object PbSendMsg : OutgoingPacketFactory<PbSendMsg.Response>("MessageSvc.PbSendMsg") {
sealed class Response : Packet {
object SUCCESS : Response() {
override fun toString(): String = "MessageSvc.PbSendMsg.Response.SUCCESS"
}
/**
* 121: 被限制? 个别号才不能发
*/
data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() {
override fun toString(): String =
"MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)"
}
}
inline fun createToFriend(
client: QQAndroidClient,
qq: Friend,
message: MessageChain,
crossinline sourceCallback: (MessageSourceToFriendImpl) -> Unit
): OutgoingPacket {
val rand = Random.nextInt().absoluteValue
val source = MessageSourceToFriendImpl(
internalId = rand,
sender = client.bot,
target = qq,
time = currentTimeSeconds.toInt(),
sequenceId = client.nextFriendSeq(),
originalMessage = message
)
sourceCallback(source)
return createToFriend(client, qq.id, message, source)
}
/**
* 发送好友消息
*/
@Suppress("FunctionName")
private fun createToFriend(
client: QQAndroidClient,
toUin: Long,
message: MessageChain,
source: MessageSourceToFriendImpl
): OutgoingPacket = buildOutgoingUniPacket(client) {
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
///return@buildOutgoingUniPacket
writeProtoBuf(
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = toUin)),
contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = message.toRichTextElems(forGroup = false, withGeneralFlags = true)
)
),
msgSeq = source.sequenceId,
msgRand = source.internalId,
syncCookie = SyncCookie(time = source.time.toLong()).toByteArray(SyncCookie.serializer())
// msgVia = 1
)
)
}
inline fun createToTemp(
client: QQAndroidClient,
member: Member,
message: MessageChain,
sourceCallback: (MessageSourceToTempImpl) -> Unit
): OutgoingPacket {
val source = MessageSourceToTempImpl(
internalId = Random.nextInt().absoluteValue,
sender = client.bot,
target = member,
time = currentTimeSeconds.toInt(),
sequenceId = client.atomicNextMessageSequenceId(),
originalMessage = message
)
sourceCallback(source)
return createToTemp(client, (member.group as GroupImpl).uin, member.id, message, source)
}
/**
* 发送临时消息
*/
private fun createToTemp(
client: QQAndroidClient,
groupUin: Long,
toUin: Long,
message: MessageChain,
source: MessageSourceToTempImpl
): OutgoingPacket = buildOutgoingUniPacket(client) {
writeProtoBuf(
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
routingHead = MsgSvc.RoutingHead(
grpTmp = MsgSvc.GrpTmp(groupUin, toUin)
),
contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = message.toRichTextElems(forGroup = false, withGeneralFlags = true)
)
),
msgSeq = source.sequenceId,
msgRand = source.internalId,
syncCookie = SyncCookie(time = source.time.toLong()).toByteArray(SyncCookie.serializer())
)
)
}
inline fun createToGroup(
client: QQAndroidClient,
group: Group,
message: MessageChain,
isForward: Boolean,
sourceCallback: (MessageSourceToGroupImpl) -> Unit
): OutgoingPacket {
val source = MessageSourceToGroupImpl(
group,
internalId = Random.nextInt().absoluteValue,
sender = client.bot,
target = group,
time = currentTimeSeconds.toInt(),
originalMessage = message//,
// sourceMessage = message
)
sourceCallback(source)
return createToGroup(client, group.id, message, isForward, source)
}
/**
* 发送群消息
*/
@Suppress("FunctionName")
private fun createToGroup(
client: QQAndroidClient,
groupCode: Long,
message: MessageChain,
isForward: Boolean,
source: MessageSourceToGroupImpl
): OutgoingPacket = buildOutgoingUniPacket(client) {
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
// DebugLogger.debug("sending group message: " + message.toRichTextElems().contentToString())
///return@buildOutgoingUniPacket
writeProtoBuf(
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
routingHead = MsgSvc.RoutingHead(grp = MsgSvc.Grp(groupCode = groupCode)),
contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = message.toRichTextElems(forGroup = true, withGeneralFlags = true),
ptt = message.firstOrNull(PttMessage)?.run {
ImMsgBody.Ptt(fileName = fileName.toByteArray(), fileMd5 = md5)
}
)
),
msgSeq = client.atomicNextMessageSequenceId(),
msgRand = source.internalId,
syncCookie = EMPTY_BYTE_ARRAY,
msgVia = 1,
msgCtrl = if (isForward) MsgCtrl(
msgFlag = 4
) else null
)
)
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
val response = readProtoBuf(MsgSvc.PbSendMsgResp.serializer())
return if (response.result == 0) {
Response.SUCCESS
} else {
Response.Failed(response.result, response.errtype, response.errmsg)
}
}
}
internal object Del : OutgoingPacketFactory<Nothing?>("MessageSvc.PbDeleteMsg") {
internal operator fun invoke(client: QQAndroidClient, items: List<MsgSvc.PbDeleteMsgReq.MsgItem>) =
buildOutgoingUniPacket(client) {
writeProtoBuf(
MsgSvc.PbDeleteMsgReq.serializer(),
MsgSvc.PbDeleteMsgReq(
msgItems = items
)
)
}
internal suspend fun delete(bot: QQAndroidBot, messages: Flow<MsgComm.Msg>) =
bot.network.run {
val map = messages.map {
MsgSvc.PbDeleteMsgReq.MsgItem(
fromUin = it.msgHead.fromUin,
toUin = it.msgHead.toUin,
// 群为84、好友为187。群通过其他方法删除但测试结果显示通过187也能删除群消息。
msgType = 187,
msgSeq = it.msgHead.msgSeq,
msgUid = it.msgHead.msgUid
)
}.toList()
Del(bot.client, map).sendWithoutExpect()
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot) = null
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright 2020 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.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.nameCardOrNick
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.MemberCardChangeEvent
import net.mamoe.mirai.getGroupOrNull
import net.mamoe.mirai.message.GroupMessageEvent
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.GroupImpl
import net.mamoe.mirai.qqandroid.contact.MemberImpl
import net.mamoe.mirai.qqandroid.message.toMessageChain
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgOnlinePush
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Oidb0x8fc
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.qqandroid.utils._miraiContentToString
import net.mamoe.mirai.qqandroid.utils.encodeToString
import net.mamoe.mirai.qqandroid.utils.io.serialization.loadAs
import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
/**
* 接受群消息
*/
internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory<Packet?>("OnlinePush.PbPushGroupMsg") {
internal class SendGroupMessageReceipt(
val messageRandom: Int,
val sequenceId: Int
) : Packet, Event, Packet.NoLog, AbstractEvent() {
override fun toString(): String {
return "OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt(messageRandom=$messageRandom, sequenceId=$sequenceId)"
}
}
@OptIn(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
// 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00
if (!bot.firstLoginSucceed) return null
val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer())
if (pbPushMsg.msg.msgHead.fromUin == bot.id) {
return SendGroupMessageReceipt(
pbPushMsg.msg.msgBody.richText.attr!!.random,
pbPushMsg.msg.msgHead.msgSeq
)
}
var extraInfo: ImMsgBody.ExtraInfo? = null
var anonymous: ImMsgBody.AnonymousGroupMsg? = null
for (elem in pbPushMsg.msg.msgBody.richText.elems) {
when {
elem.extraInfo != null -> extraInfo = elem.extraInfo
elem.anonGroupMsg != null -> anonymous = elem.anonGroupMsg
}
}
val group =
bot.getGroupOrNull(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
val sender = if (anonymous != null) {
group.newAnonymous(anonymous.anonNick.encodeToString())
} else {
group[pbPushMsg.msg.msgHead.fromUin]
} as MemberImpl
val name = if (anonymous != null) {
sender.nameCard
} else {
extraInfo?.groupCard?.takeIf { it.isNotEmpty() }?.run {
kotlin.runCatching {
if (this[0] == 0x0A.toByte())
loadAs(Oidb0x8fc.CommCardNameBuf.serializer()).richCardName?.joinToString("") { it.text.encodeToString() }
else return@runCatching null
}.getOrNull() ?: encodeToString()
} ?: pbPushMsg.msg.msgHead.groupInfo.groupCard.takeIf { it.isNotEmpty() }
?: sender.nameCardOrNick // 没有 extraInfo 就从 head 里取
}
val flags = extraInfo?.flags ?: 0
return GroupMessageEvent(
senderName = name.also {
if (it != sender.nameCard) {
val origin = sender._nameCard
sender._nameCard = name
MemberCardChangeEvent(origin, name, sender).broadcast()
}
},
sender = sender,
message = pbPushMsg.msg.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true),
permission = when {
flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
flags and 8 != 0 -> MemberPermission.OWNER
flags == 0 -> MemberPermission.MEMBER
else -> {
bot.logger.warning("判断群 ${sender.group.id} 的群员 ${sender.id} 的权限失败: ${pbPushMsg.msg.msgHead._miraiContentToString()}. 请完整截图或复制此日志并确认其真实权限后发送给 mirai 维护者以帮助解决问题.")
sender.permission
}
},
time = pbPushMsg.msg.msgHead.msgTime
)
}
}

View File

@ -0,0 +1,144 @@
/*
* Copyright 2020 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
*/
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@file:OptIn(MiraiInternalAPI::class,
MiraiExperimentalAPI::class,
JavaFriendlyAPI::class,
ExperimentalUnsignedTypes::class)
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt
import net.mamoe.mirai.JavaFriendlyAPI
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.events.BotGroupPermissionChangeEvent
import net.mamoe.mirai.event.events.MemberLeaveEvent
import net.mamoe.mirai.event.events.MemberPermissionChangeEvent
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.GroupImpl
import net.mamoe.mirai.qqandroid.contact.MemberImpl
import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.OnlinePushTrans
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.utils.read
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
internal object OnlinePushPbPushTransMsg :
IncomingPacketFactory<Packet?>("OnlinePush.PbPushTransMsg", "OnlinePush.RespPush") {
@OptIn(MiraiInternalAPI::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
val content = this.readProtoBuf(OnlinePushTrans.PbMsgInfo.serializer())
if (!bot.client.pbPushTransMsgCacheList.ensureNoDuplication(content.msgSeq)) {
return null
}
content.msgData.read<Unit> {
when (content.msgType) {
44 -> {
this.discardExact(5)
val var4 = readByte().toInt()
var var5 = 0L
val target = readUInt().toLong()
if (var4 != 0 && var4 != 1) {
var5 = readUInt().toLong()
}
val group = bot.getGroupByUin(content.fromUin) as GroupImpl
if (var5 == 0L && this.remaining == 1L) {//管理员变更
val newPermission =
if (this.readByte().toInt() == 1) MemberPermission.ADMINISTRATOR
else MemberPermission.MEMBER
if (target == bot.id) {
if (group.botPermission == newPermission) {
return null
}
return BotGroupPermissionChangeEvent(
group,
group.botPermission.also {
group.botAsMember.checkIsMemberImpl().permission = newPermission
},
newPermission
)
} else {
val member = group[target] as MemberImpl
if (member.permission == newPermission) {
return null
}
return MemberPermissionChangeEvent(
member,
member.permission.also { member.permission = newPermission },
newPermission
)
}
}
}
34 -> {
/* quit
27 0B 60 E7
01
2F 55 7C B8
82
00 30 42 33 32 46 30 38 33 32 39 32 35 30 31 39 33 45 46 32 45 30 36 35 41 35 41 33 42 37 35 43 41 34 46 37 42 38 42 38 42 44 43 35 35 34 35 44 38 30
*/
/* kick
27 0B 60 E7
01
A8 32 51 A1
83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 39 32 46 45 30 36 31 41 33 37 36 43 44 35 37 35 37 39 45 37 32 34 44 37 37 30 36 46 39 39 43 35 35 33 33 31 34 44 32 44 46 35 45 42 43 31 31 36
*/
readUInt().toLong() // group, uin or code ?
discardExact(1)
val target = readUInt().toLong()
val type = readUByte().toInt()
val operator = readUInt().toLong()
val groupUin = content.fromUin
when (type) {
0x82 -> bot.getGroupByUinOrNull(groupUin)?.let { group ->
val member = group.getOrNull(target) as? MemberImpl ?: return null
return MemberLeaveEvent.Quit(member.also {
group.members.delegate.remove(member)
})
}
0x83 -> bot.getGroupByUin(groupUin).let { group ->
val member = group.getOrNull(target) as? MemberImpl ?: return null
return MemberLeaveEvent.Kick(member.also {
group.members.delegate.remove(member)
}, group.members[operator])
}
}
}
}
}
return null
}
override suspend fun QQAndroidBot.handle(packet: Packet?, sequenceId: Int): OutgoingPacket? {
return buildResponseUniPacket(client, sequenceId = sequenceId) {}
}
}

View File

@ -0,0 +1,518 @@
/*
* Copyright 2020 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
*/
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@file:OptIn(MiraiInternalAPI::class,
MiraiExperimentalAPI::class,
JavaFriendlyAPI::class,
ExperimentalUnsignedTypes::class)
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUInt
import kotlinx.serialization.Serializable
import net.mamoe.mirai.*
import net.mamoe.mirai.data.FriendInfo
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.GroupImpl
import net.mamoe.mirai.qqandroid.contact.checkIsGroupImpl
import net.mamoe.mirai.qqandroid.contact.checkIsInstance
import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl
import net.mamoe.mirai.qqandroid.message.contextualBugReportException
import net.mamoe.mirai.qqandroid.network.MultiPacketBySequence
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.MsgInfo
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.MsgType0x210
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.OnlinePushPack
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x27.SubMsgType0x27.*
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x44
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0xb3
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.TroopTips0x857
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePushReqPush.ignoredLambda528
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePushReqPush.lambda528
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePushReqPush.lambda732
import net.mamoe.mirai.qqandroid.utils._miraiContentToString
import net.mamoe.mirai.qqandroid.utils.encodeToString
import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf
import net.mamoe.mirai.qqandroid.utils.io.readString
import net.mamoe.mirai.qqandroid.utils.io.serialization.*
import net.mamoe.mirai.qqandroid.utils.read
import net.mamoe.mirai.qqandroid.utils.toUHexString
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.debug
//0C 01 B1 89 BE 09 5E 3D 72 A6 00 01 73 68 FC 06 00 00 00 3C
internal object OnlinePushReqPush : IncomingPacketFactory<OnlinePushReqPush.Response>(
"OnlinePush.ReqPush",
"OnlinePush.RespPush"
) {
// to reduce nesting depth
private fun List<MsgInfo>.deco(
client: QQAndroidClient,
mapper: ByteReadPacket.(msgInfo: MsgInfo) -> Sequence<Packet>
): Sequence<Packet> {
return asSequence().filter { msg ->
client.onlinePushCacheList.ensureNoDuplication(msg.shMsgSeq)
}.flatMap { it.vMsg.read { mapper(it) } }
}
@Suppress("unused") // bug
private fun lambda732(block: ByteReadPacket.(group: GroupImpl, bot: QQAndroidBot) -> Sequence<Packet>):
ByteReadPacket.(group: GroupImpl, bot: QQAndroidBot) -> Sequence<Packet> {
return block
}
private fun lambda528(block: MsgType0x210.(bot: QQAndroidBot) -> Sequence<Packet>):
MsgType0x210.(bot: QQAndroidBot) -> Sequence<Packet> {
return block
}
val ignoredLambda528: MsgType0x210.(bot: QQAndroidBot) -> Sequence<Packet> = lambda528 { emptySequence() }
@ExperimentalUnsignedTypes
@OptIn(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Response {
val reqPushMsg = readUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req")
val packets: Sequence<Packet> = reqPushMsg.vMsgInfos.deco(bot.client) { msgInfo ->
when (msgInfo.shMsgType.toInt()) {
732 -> {
val group = bot.getGroup(readUInt().toLong())
GroupImpl.checkIsInstance(group)
val internalType = readByte().toInt()
discardExact(1)
Transformers732[internalType]
?.let { it(this@deco, group, bot) }
?: kotlin.run {
bot.network.logger.debug {
"unknown group 732 type $internalType, data: " + readBytes().toUHexString()
}
return@deco emptySequence()
}
}
// 00 27 1A 0C 1C 2C 3C 4C 5D 00 0C 6D 00 0C 7D 00 0C 8D 00 0C 9C AC BC CC DD 00 0C EC FC 0F 0B 2A 0C 1C 2C 3C 4C 5C 6C 0B 3A 0C 1C 2C 3C 4C 5C 6C 7C 8D 00 0C 9D 00 0C AC BD 00 0C CD 00 0C DC ED 00 0C FC 0F FC 10 0B 4A 0C 1C 2C 3C 4C 5C 6C 7C 8C 96 00 0B 5A 0C 1C 2C 3C 4C 5C 6C 7C 8C 9D 00 0C 0B 6A 0C 1A 0C 1C 26 00 0B 2A 0C 0B 3A 0C 16 00 0B 4A 09 0C 0B 5A 09 0C 0B 0B 7A 0C 1C 2C 36 00 0B 8A 0C 1C 2C 36 00 0B 9A 09 0C 0B AD 00 00 1E 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
528 -> {
val notifyMsgBody = readJceStruct(MsgType0x210.serializer())
Transformers528[notifyMsgBody.uSubMsgType]
?.let { processor -> processor(notifyMsgBody, bot) }
?: kotlin.run {
bot.network.logger.debug {
// Network(1994701021) 16:03:54 : unknown group 528 type 0x0000000000000026, data: 08 01 12 40 0A 06 08 F4 EF BB 8F 04 10 E7 C1 AD B8 02 18 01 22 2C 10 01 1A 1A 18 B4 DC F8 9B 0C 20 E7 C1 AD B8 02 28 06 30 02 A2 01 04 08 93 D6 03 A8 01 08 20 00 28 00 32 08 18 01 20 FE AF AF F5 05 28 00
// VIP 进群提示
"unknown group 528 type 0x${notifyMsgBody.uSubMsgType.toUHexString("")}, data: " + notifyMsgBody.vProtobuf.toUHexString()
}
return@deco emptySequence()
}
}
else -> {
bot.network.logger.debug { "unknown sh type ${msgInfo.shMsgType.toInt()}" }
bot.network.logger.debug { "data=${readBytes().toUHexString()}" }
return@deco emptySequence()
}
}
}
return Response(reqPushMsg, packets)
}
@Suppress("SpellCheckingInspection")
internal data class Response(val request: OnlinePushPack.SvcReqPushMsg, val sequence: Sequence<Packet>) :
MultiPacketBySequence<Packet>(sequence) {
override fun toString(): String {
return "OnlinePush.ReqPush.Response"
}
}
override suspend fun QQAndroidBot.handle(packet: Response, sequenceId: Int): OutgoingPacket? {
return buildResponseUniPacket(client) {
writeJceStruct(
RequestPacket.serializer(),
RequestPacket(
sServantName = "OnlinePush",
sFuncName = "SvcRespPushMsg",
iRequestId = sequenceId,
sBuffer = jceRequestSBuffer(
"resp",
OnlinePushPack.SvcRespPushMsg.serializer(),
OnlinePushPack.SvcRespPushMsg(
packet.request.uin,
packet.request.vMsgInfos.map { msg ->
OnlinePushPack.DelMsgInfo(
fromUin = msg.lFromUin,
shMsgSeq = msg.shMsgSeq,
vMsgCookies = msg.vMsgCookies,
uMsgTime = msg.uMsgTime // captured 0
)
}
)
)
)
)
}
}
}
private object Transformers732 : Map<Int, ByteReadPacket.(GroupImpl, QQAndroidBot) -> Sequence<Packet>> by mapOf(
// mute
0x0c to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
val operatorUin = readUInt().toLong()
if (operatorUin == bot.id) {
return@lambda732 emptySequence()
}
val operator = group.getOrNull(operatorUin) ?: return@lambda732 emptySequence()
readUInt().toLong() // time
this.discardExact(2)
val target = readUInt().toLong()
val timeSeconds = readInt()
if (target == 0L) {
val new = timeSeconds != 0
if (group.settings.isMuteAll == new) {
return@lambda732 emptySequence()
}
group._muteAll = new
return@lambda732 sequenceOf(GroupMuteAllEvent(!new, new, group, operator))
}
if (target == bot.id) {
return@lambda732 when {
group.botMuteRemaining == timeSeconds -> emptySequence()
timeSeconds == 0 || timeSeconds == 0xFFFF_FFFF.toInt() -> {
group.botAsMember.checkIsMemberImpl()._muteTimestamp = 0
sequenceOf(BotUnmuteEvent(operator))
}
else -> {
group.botAsMember.checkIsMemberImpl()._muteTimestamp =
currentTimeSeconds.toInt() + timeSeconds
sequenceOf(BotMuteEvent(timeSeconds, operator))
}
}
}
val member = group.getOrNull(target) ?: return@lambda732 emptySequence()
member.checkIsMemberImpl()
if (member._muteTimestamp == timeSeconds) {
return@lambda732 emptySequence()
}
member._muteTimestamp = timeSeconds
return@lambda732 if (timeSeconds == 0) sequenceOf(MemberUnmuteEvent(member, operator))
else sequenceOf(MemberMuteEvent(member, timeSeconds, operator))
},
// anonymous
0x0e to lambda732 { group: GroupImpl, _: QQAndroidBot ->
// 匿名
val operator = group.getOrNull(readUInt().toLong()) ?: return@lambda732 emptySequence()
val new = readInt() == 0
if (group.settings.isAnonymousChatEnabled == new) {
return@lambda732 emptySequence()
}
group._anonymousChat = new
return@lambda732 sequenceOf(GroupAllowAnonymousChatEvent(!new, new, group, operator))
},
// 传字符串信息
0x10 to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
val dataBytes = readBytes(26)
when (dataBytes[0].toInt()) {
59 -> { // TODO 应该在 Transformers528 处理
val size = readByte().toInt() // orthodox, don't `readUByte`
if (size < 0) {
// java.lang.IllegalStateException: negative array size: -100, remaining bytes=B0 E6 99 90 D8 E8 02 98 06 01
// java.lang.IllegalStateException: negative array size: -121, remaining bytes=03 10 D9 F7 A2 93 0D 18 E0 DB E8 CA 0B 32 22 61 34 64 31 34 64 61 64 65 65 38 32 32 34 62 64 32 35 34 65 63 37 62 62 30 33 30 66 61 36 66 61 6D 6A 38 0E 48 00 58 01 70 C8 E8 9B 07 7A AD 02 3C 7B 22 69 63 6F 6E 22 3A 22 71 71 77 61 6C 6C 65 74 5F 63 75 73 74 6F 6D 5F 74 69 70 73 5F 69 64 69 6F 6D 5F 69 63 6F 6E 2E 70 6E 67 22 2C 22 61 6C 74 22 3A 22 22 7D 3E 3C 7B 22 63 6D 64 22 3A 31 2C 22 64 61 74 61 22 3A 22 6C 69 73 74 69 64 3D 31 30 30 30 30 34 35 32 30 31 32 30 30 34 30 38 31 32 30 30 31 30 39 36 31 32 33 31 34 35 30 30 26 67 72 6F 75 70 74 79 70 65 3D 31 22 2C 22 74 65 78 74 43 6F 6C 6F 72 22 3A 22 30 78 38 37 38 42 39 39 22 2C 22 74 65 78 74 22 3A 22 E6 8E A5 E9 BE 99 E7 BA A2 E5 8C 85 E4 B8 8B E4 B8 80 E4 B8 AA E6 8B BC E9 9F B3 EF BC 9A 22 7D 3E 3C 7B 22 63 6D 64 22 3A 31 2C 22 64 61 74 61 22 3A 22 6C 69 73 74 69 64 3D 31 30 30 30 30 34 35 32 30 31 32 30 30 34 30 38 31 32 30 30 31 30 39 36 31 32 33 31 34 35 30 30 26 67 72 6F 75 70 74 79 70 65 3D 31 22 2C 22 74 65 78 74 43 6F 6C 6F 72 22 3A 22 30 78 45 36 32 35 35 35 22 2C 22 74 65 78 74 22 3A 22 64 69 6E 67 22 7D 3E 82 01 0C E8 80 81 E5 83 A7 E5 85 A5 E5 AE 9A 88 01 03 92 01 04 64 69 6E 67 A0 01 00
// negative array size: -40, remaining bytes=D6 94 C3 8C D8 E8 02 98 06 01
error("negative array size: $size, remaining bytes=${readBytes().toUHexString()}")
}
// println(dataBytes.toUHexString())
//println(message + ":" + dataBytes.toUHexString())
val new = when (val message = readString(size)) {
"管理员已关闭群聊坦白说" -> false
"管理员已开启群聊坦白说" -> true
else -> {
bot.network.logger.debug { "Unknown server messages $message" }
return@lambda732 emptySequence()
}
}
if (group.settings.isConfessTalkEnabled == new) {
return@lambda732 emptySequence()
}
return@lambda732 sequenceOf(
GroupAllowConfessTalkEvent(
new,
false,
group,
false
)
)
}
0x2D -> {
// 修改群名. 在 Transformers528 0x27L 处理
return@lambda732 emptySequence()
}
else -> {
/*
bot.network.logger.debug("unknown Transformer732 0xunknown type: ${dataBytes[0].toString(16)
.toUpperCase()}")
bot.network.logger.debug("unknown Transformer732 0xdata= ${readBytes().toUHexString()}")
*/
return@lambda732 emptySequence()
/*
if (group.name == message) {
return@lambda732 emptySequence()
}
return@lambda732 sequenceOf(
GroupNameChangeEvent(
group.name.also { group._name = message },
message, group, false
)
)*/
}
}
},
// recall
0x11 to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
discardExact(1)
val proto = readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer())
val recallReminder = proto.optMsgRecall ?: return@lambda732 emptySequence()
val operator =
if (recallReminder.uin == bot.id) group.botAsMember
else group.getOrNull(recallReminder.uin) ?: return@lambda732 emptySequence()
return@lambda732 recallReminder.recalledMsgList.asSequence().mapNotNull { pkg ->
when {
pkg.authorUin == bot.id && operator.id == bot.id -> null
else -> {
MessageRecallEvent.GroupRecall(
bot,
pkg.authorUin,
pkg.seq,
pkg.msgRandom,
pkg.time,
operator,
group
)
}
}
}
}
)
// uSubMsgType to vProtobuf
// 138 or 139: top_package/akln.java:1568
// 66: top_package/nhz.java:269
/**
* @see MsgType0x210
*/
@OptIn(LowLevelAPI::class, MiraiInternalAPI::class)
private object Transformers528 : Map<Long, MsgType0x210.(QQAndroidBot) -> Sequence<Packet>> 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 = bot._lowLevelNewFriend(object : FriendInfo {
override val uin: Long get() = body.msgAddFrdNotify.fuin
override val nick: String get() = body.msgAddFrdNotify.fuinNick
})
bot.friends.delegate.addLast(new)
return@lambda528 sequenceOf(FriendAddEvent(new))
},
0xE2L to lambda528 {
// TODO: unknown. maybe messages.
// 0A 35 08 00 10 A2 FF 8C F0 03 1A 1B E5 90 8C E6 84 8F E4 BD A0 E7 9A 84 E5 8A A0 E5 A5 BD E5 8F 8B E8 AF B7 E6 B1 82 22 0C E6 BD 9C E6 B1 9F E7 BE A4 E5 8F 8B 28 01
// vProtobuf.loadAs(Msgtype0x210.serializer())
return@lambda528 emptySequence()
},
0x44L to lambda528 { bot ->
val msg = vProtobuf.loadAs(Submsgtype0x44.Submsgtype0x44.MsgBody.serializer())
when {
msg.msgCleanCountMsg != null -> {
}
msg.msgFriendMsgSync != null -> {
}
else -> {
bot.network.logger.debug { "OnlinePush528 0x44L: " + msg._miraiContentToString() }
}
}
return@lambda528 emptySequence()
},
// bot 在其他客户端被踢或主动退出而同步情况
0xD4L to lambda528 { bot ->
@Serializable
data class SubD4(
// ok
val uin: Long
) : ProtoBuf
val uin = vProtobuf.loadAs(SubD4.serializer()).uin
val group = bot.getGroupByUinOrNull(uin) ?: bot.getGroupOrNull(uin)
return@lambda528 if (group != null && bot.groups.delegate.remove(group)) {
sequenceOf(BotLeaveEvent.Active(group))
} else emptySequence()
},
// 群相关, ModFriendRemark, DelFriend, ModGroupProfile
0x27L to lambda528 { bot ->
fun ModFriendRemark.transform(bot: QQAndroidBot): Sequence<Packet> {
return this.msgFrdRmk?.asSequence()?.mapNotNull {
val friend = bot.getFriendOrNull(it.fuin) ?: return@mapNotNull null
// TODO: 2020/4/10 ADD REMARK QUERY
FriendRemarkChangeEvent(bot, friend, it.rmkName)
} ?: emptySequence()
}
fun DelFriend.transform(bot: QQAndroidBot): Sequence<Packet> {
return this.uint64Uins?.asSequence()?.mapNotNull {
val friend = bot.getFriendOrNull(it) ?: return@mapNotNull null
if (bot.friends.delegate.remove(friend)) {
FriendDeleteEvent(friend)
} else null
} ?: emptySequence()
}
fun ModGroupProfile.transform(bot: QQAndroidBot): Sequence<Packet> {
return this.msgGroupProfileInfos?.asSequence()?.mapNotNull { info ->
when (info.field) {
1 -> {
// 群名
val new = info.value.encodeToString()
val group = bot.getGroupOrNull(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.getOrNull(this.cmdUin) ?: return@mapNotNull null
group._name = 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
}
} ?: emptySequence()
}
fun ModGroupMemberProfile.transform(bot: QQAndroidBot): Sequence<Packet> {
return this.msgGroupMemberProfileInfos?.asSequence()?.mapNotNull { info ->
when (info.field) {
1 -> { // name card
val new = info.value
val group = bot.getGroupOrNull(this.groupCode) ?: return@mapNotNull null
group.checkIsGroupImpl()
val member = group.getOrNull(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()?.toInt() != 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
}
}
} ?: emptySequence()
}
fun ModCustomFace.transform(): Sequence<Packet> =
sequenceOf(BotFaceChangedEvent(Bot.getInstance(uin)))
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.msgModGroupProfile != null -> it.msgModGroupProfile.transform(bot)
it.msgModGroupMemberProfile != null -> it.msgModGroupMemberProfile.transform(bot)
it.msgModCustomFace != null -> it.msgModCustomFace.transform()
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
}
)

View File

@ -1,698 +0,0 @@
/*
* Copyright 2020 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
*/
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.*
import kotlinx.serialization.Serializable
import net.mamoe.mirai.Bot
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.nameCardOrNick
import net.mamoe.mirai.data.FriendInfo
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.getGroupOrNull
import net.mamoe.mirai.message.GroupMessageEvent
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.*
import net.mamoe.mirai.qqandroid.message.contextualBugReportException
import net.mamoe.mirai.qqandroid.message.toMessageChain
import net.mamoe.mirai.qqandroid.network.MultiPacketBySequence
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.MsgInfo
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.MsgType0x210
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.OnlinePushPack
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.qqandroid.utils._miraiContentToString
import net.mamoe.mirai.qqandroid.utils.encodeToString
import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf
import net.mamoe.mirai.qqandroid.utils.io.readString
import net.mamoe.mirai.qqandroid.utils.io.serialization.*
import net.mamoe.mirai.qqandroid.utils.read
import net.mamoe.mirai.qqandroid.utils.toUHexString
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.debug
internal class OnlinePush {
/**
* 接受群消息
*/
internal object PbPushGroupMsg : IncomingPacketFactory<Packet?>("OnlinePush.PbPushGroupMsg") {
internal class SendGroupMessageReceipt(
val messageRandom: Int,
val sequenceId: Int
) : Packet, Event, Packet.NoLog, AbstractEvent() {
override fun toString(): String {
return "OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt(messageRandom=$messageRandom, sequenceId=$sequenceId)"
}
}
@OptIn(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
// 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00
if (!bot.firstLoginSucceed) return null
val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer())
if (pbPushMsg.msg.msgHead.fromUin == bot.id) {
return SendGroupMessageReceipt(
pbPushMsg.msg.msgBody.richText.attr!!.random,
pbPushMsg.msg.msgHead.msgSeq
)
}
var extraInfo: ImMsgBody.ExtraInfo? = null
var anonymous: ImMsgBody.AnonymousGroupMsg? = null
for (elem in pbPushMsg.msg.msgBody.richText.elems) {
when {
elem.extraInfo != null -> extraInfo = elem.extraInfo
elem.anonGroupMsg != null -> anonymous = elem.anonGroupMsg
}
}
val group =
bot.getGroupOrNull(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
val sender = if (anonymous != null) {
group.newAnonymous(anonymous.anonNick.encodeToString())
} else {
group[pbPushMsg.msg.msgHead.fromUin]
} as MemberImpl
val name = if (anonymous != null) {
sender.nameCard
} else {
extraInfo?.groupCard?.takeIf { it.isNotEmpty() }?.run {
kotlin.runCatching {
if (this[0] == 0x0A.toByte())
loadAs(Oidb0x8fc.CommCardNameBuf.serializer()).richCardName?.joinToString("") { it.text.encodeToString() }
else return@runCatching null
}.getOrNull() ?: encodeToString()
} ?: pbPushMsg.msg.msgHead.groupInfo.groupCard.takeIf { it.isNotEmpty() }
?: sender.nameCardOrNick // 没有 extraInfo 就从 head 里取
}
val flags = extraInfo?.flags ?: 0
return GroupMessageEvent(
senderName = name.also {
if (it != sender.nameCard) {
val origin = sender._nameCard
sender._nameCard = name
MemberCardChangeEvent(origin, name, sender).broadcast()
}
},
sender = sender,
message = pbPushMsg.msg.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true),
permission = when {
flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
flags and 8 != 0 -> MemberPermission.OWNER
flags == 0 -> MemberPermission.MEMBER
else -> {
bot.logger.warning("判断群 ${sender.group.id} 的群员 ${sender.id} 的权限失败: ${pbPushMsg.msg.msgHead._miraiContentToString()}. 请完整截图或复制此日志并确认其真实权限后发送给 mirai 维护者以帮助解决问题.")
sender.permission
}
},
time = pbPushMsg.msg.msgHead.msgTime
)
}
}
internal object PbPushTransMsg :
IncomingPacketFactory<Packet?>("OnlinePush.PbPushTransMsg", "OnlinePush.RespPush") {
@OptIn(MiraiInternalAPI::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
val content = this.readProtoBuf(OnlinePushTrans.PbMsgInfo.serializer())
if (!bot.client.pbPushTransMsgCacheList.ensureNoDuplication(content.msgSeq)) {
return null
}
content.msgData.read<Unit> {
when (content.msgType) {
44 -> {
this.discardExact(5)
val var4 = readByte().toInt()
var var5 = 0L
val target = readUInt().toLong()
if (var4 != 0 && var4 != 1) {
var5 = readUInt().toLong()
}
val group = bot.getGroupByUin(content.fromUin) as GroupImpl
if (var5 == 0L && this.remaining == 1L) {//管理员变更
val newPermission =
if (this.readByte().toInt() == 1) MemberPermission.ADMINISTRATOR
else MemberPermission.MEMBER
if (target == bot.id) {
if (group.botPermission == newPermission) {
return null
}
return BotGroupPermissionChangeEvent(
group,
group.botPermission.also {
group.botAsMember.checkIsMemberImpl().permission = newPermission
},
newPermission
)
} else {
val member = group[target] as MemberImpl
if (member.permission == newPermission) {
return null
}
return MemberPermissionChangeEvent(
member,
member.permission.also { member.permission = newPermission },
newPermission
)
}
}
}
34 -> {
/* quit
27 0B 60 E7
01
2F 55 7C B8
82
00 30 42 33 32 46 30 38 33 32 39 32 35 30 31 39 33 45 46 32 45 30 36 35 41 35 41 33 42 37 35 43 41 34 46 37 42 38 42 38 42 44 43 35 35 34 35 44 38 30
*/
/* kick
27 0B 60 E7
01
A8 32 51 A1
83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 39 32 46 45 30 36 31 41 33 37 36 43 44 35 37 35 37 39 45 37 32 34 44 37 37 30 36 46 39 39 43 35 35 33 33 31 34 44 32 44 46 35 45 42 43 31 31 36
*/
readUInt().toLong() // group, uin or code ?
discardExact(1)
val target = readUInt().toLong()
val type = readUByte().toInt()
val operator = readUInt().toLong()
val groupUin = content.fromUin
when (type) {
0x82 -> bot.getGroupByUinOrNull(groupUin)?.let { group ->
val member = group.getOrNull(target) as? MemberImpl ?: return null
return MemberLeaveEvent.Quit(member.also {
group.members.delegate.remove(member)
})
}
0x83 -> bot.getGroupByUin(groupUin).let { group ->
val member = group.getOrNull(target) as? MemberImpl ?: return null
return MemberLeaveEvent.Kick(member.also {
group.members.delegate.remove(member)
}, group.members[operator])
}
}
}
}
}
return null
}
override suspend fun QQAndroidBot.handle(packet: Packet?, sequenceId: Int): OutgoingPacket? {
return buildResponseUniPacket(client, sequenceId = sequenceId) {}
}
}
//0C 01 B1 89 BE 09 5E 3D 72 A6 00 01 73 68 FC 06 00 00 00 3C
internal object ReqPush : IncomingPacketFactory<ReqPush.Response>(
"OnlinePush.ReqPush",
"OnlinePush.RespPush"
) {
// to reduce nesting depth
private fun List<MsgInfo>.deco(
client: QQAndroidClient,
mapper: ByteReadPacket.(msgInfo: MsgInfo) -> Sequence<Packet>
): Sequence<Packet> {
return asSequence().filter { msg ->
client.onlinePushCacheList.ensureNoDuplication(msg.shMsgSeq)
}.flatMap { it.vMsg.read { mapper(it) } }
}
private fun lambda732(block: ByteReadPacket.(group: GroupImpl, bot: QQAndroidBot) -> Sequence<Packet>):
ByteReadPacket.(group: GroupImpl, bot: QQAndroidBot) -> Sequence<Packet> {
return block
}
object Transformers732 : Map<Int, ByteReadPacket.(GroupImpl, QQAndroidBot) -> Sequence<Packet>> by mapOf(
// mute
0x0c to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
val operatorUin = readUInt().toLong()
if (operatorUin == bot.id) {
return@lambda732 emptySequence()
}
val operator = group.getOrNull(operatorUin) ?: return@lambda732 emptySequence()
readUInt().toLong() // time
this.discardExact(2)
val target = readUInt().toLong()
val timeSeconds = readInt()
if (target == 0L) {
val new = timeSeconds != 0
if (group.settings.isMuteAll == new) {
return@lambda732 emptySequence()
}
group._muteAll = new
return@lambda732 sequenceOf(GroupMuteAllEvent(!new, new, group, operator))
}
if (target == bot.id) {
return@lambda732 when {
group.botMuteRemaining == timeSeconds -> emptySequence()
timeSeconds == 0 || timeSeconds == 0xFFFF_FFFF.toInt() -> {
group.botAsMember.checkIsMemberImpl()._muteTimestamp = 0
sequenceOf(BotUnmuteEvent(operator))
}
else -> {
group.botAsMember.checkIsMemberImpl()._muteTimestamp =
currentTimeSeconds.toInt() + timeSeconds
sequenceOf(BotMuteEvent(timeSeconds, operator))
}
}
}
val member = group.getOrNull(target) ?: return@lambda732 emptySequence()
member.checkIsMemberImpl()
if (member._muteTimestamp == timeSeconds) {
return@lambda732 emptySequence()
}
member._muteTimestamp = timeSeconds
return@lambda732 if (timeSeconds == 0) sequenceOf(MemberUnmuteEvent(member, operator))
else sequenceOf(MemberMuteEvent(member, timeSeconds, operator))
},
// anonymous
0x0e to lambda732 { group: GroupImpl, _: QQAndroidBot ->
// 匿名
val operator = group.getOrNull(readUInt().toLong()) ?: return@lambda732 emptySequence()
val new = readInt() == 0
if (group.settings.isAnonymousChatEnabled == new) {
return@lambda732 emptySequence()
}
group._anonymousChat = new
return@lambda732 sequenceOf(GroupAllowAnonymousChatEvent(!new, new, group, operator))
},
// 传字符串信息
0x10 to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
val dataBytes = readBytes(26)
when (dataBytes[0].toInt()) {
59 -> { // TODO 应该在 Transformers528 处理
val size = readByte().toInt() // orthodox, don't `readUByte`
if (size < 0) {
// java.lang.IllegalStateException: negative array size: -100, remaining bytes=B0 E6 99 90 D8 E8 02 98 06 01
// java.lang.IllegalStateException: negative array size: -121, remaining bytes=03 10 D9 F7 A2 93 0D 18 E0 DB E8 CA 0B 32 22 61 34 64 31 34 64 61 64 65 65 38 32 32 34 62 64 32 35 34 65 63 37 62 62 30 33 30 66 61 36 66 61 6D 6A 38 0E 48 00 58 01 70 C8 E8 9B 07 7A AD 02 3C 7B 22 69 63 6F 6E 22 3A 22 71 71 77 61 6C 6C 65 74 5F 63 75 73 74 6F 6D 5F 74 69 70 73 5F 69 64 69 6F 6D 5F 69 63 6F 6E 2E 70 6E 67 22 2C 22 61 6C 74 22 3A 22 22 7D 3E 3C 7B 22 63 6D 64 22 3A 31 2C 22 64 61 74 61 22 3A 22 6C 69 73 74 69 64 3D 31 30 30 30 30 34 35 32 30 31 32 30 30 34 30 38 31 32 30 30 31 30 39 36 31 32 33 31 34 35 30 30 26 67 72 6F 75 70 74 79 70 65 3D 31 22 2C 22 74 65 78 74 43 6F 6C 6F 72 22 3A 22 30 78 38 37 38 42 39 39 22 2C 22 74 65 78 74 22 3A 22 E6 8E A5 E9 BE 99 E7 BA A2 E5 8C 85 E4 B8 8B E4 B8 80 E4 B8 AA E6 8B BC E9 9F B3 EF BC 9A 22 7D 3E 3C 7B 22 63 6D 64 22 3A 31 2C 22 64 61 74 61 22 3A 22 6C 69 73 74 69 64 3D 31 30 30 30 30 34 35 32 30 31 32 30 30 34 30 38 31 32 30 30 31 30 39 36 31 32 33 31 34 35 30 30 26 67 72 6F 75 70 74 79 70 65 3D 31 22 2C 22 74 65 78 74 43 6F 6C 6F 72 22 3A 22 30 78 45 36 32 35 35 35 22 2C 22 74 65 78 74 22 3A 22 64 69 6E 67 22 7D 3E 82 01 0C E8 80 81 E5 83 A7 E5 85 A5 E5 AE 9A 88 01 03 92 01 04 64 69 6E 67 A0 01 00
// negative array size: -40, remaining bytes=D6 94 C3 8C D8 E8 02 98 06 01
error("negative array size: $size, remaining bytes=${readBytes().toUHexString()}")
}
// println(dataBytes.toUHexString())
//println(message + ":" + dataBytes.toUHexString())
val new = when (val message = readString(size)) {
"管理员已关闭群聊坦白说" -> false
"管理员已开启群聊坦白说" -> true
else -> {
bot.network.logger.debug { "Unknown server messages $message" }
return@lambda732 emptySequence()
}
}
if (group.settings.isConfessTalkEnabled == new) {
return@lambda732 emptySequence()
}
return@lambda732 sequenceOf(
GroupAllowConfessTalkEvent(
new,
false,
group,
false
)
)
}
0x2D -> {
// 修改群名. 在 Transformers528 0x27L 处理
return@lambda732 emptySequence()
}
else -> {
/*
bot.network.logger.debug("unknown Transformer732 0xunknown type: ${dataBytes[0].toString(16)
.toUpperCase()}")
bot.network.logger.debug("unknown Transformer732 0xdata= ${readBytes().toUHexString()}")
*/
return@lambda732 emptySequence()
/*
if (group.name == message) {
return@lambda732 emptySequence()
}
return@lambda732 sequenceOf(
GroupNameChangeEvent(
group.name.also { group._name = message },
message, group, false
)
)*/
}
}
},
// recall
0x11 to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
discardExact(1)
val proto = readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer())
val recallReminder = proto.optMsgRecall ?: return@lambda732 emptySequence()
val operator =
if (recallReminder.uin == bot.id) group.botAsMember
else group.getOrNull(recallReminder.uin) ?: return@lambda732 emptySequence()
return@lambda732 recallReminder.recalledMsgList.asSequence().mapNotNull { pkg ->
when {
pkg.authorUin == bot.id && operator.id == bot.id -> null
else -> {
MessageRecallEvent.GroupRecall(
bot,
pkg.authorUin,
pkg.seq,
pkg.msgRandom,
pkg.time,
operator,
group
)
}
}
}
}
)
private fun lambda528(block: MsgType0x210.(bot: QQAndroidBot) -> Sequence<Packet>):
MsgType0x210.(bot: QQAndroidBot) -> Sequence<Packet> {
return block
}
val ignoredLambda528: MsgType0x210.(bot: QQAndroidBot) -> Sequence<Packet> = lambda528 { emptySequence() }
// uSubMsgType to vProtobuf
// 138 or 139: top_package/akln.java:1568
// 66: top_package/nhz.java:269
/**
* @see MsgType0x210
*/
@OptIn(LowLevelAPI::class, MiraiInternalAPI::class)
object Transformers528 : Map<Long, MsgType0x210.(QQAndroidBot) -> Sequence<Packet>> 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 = bot._lowLevelNewFriend(object : FriendInfo {
override val uin: Long get() = body.msgAddFrdNotify.fuin
override val nick: String get() = body.msgAddFrdNotify.fuinNick
})
bot.friends.delegate.addLast(new)
return@lambda528 sequenceOf(FriendAddEvent(new))
},
0xE2L to lambda528 {
// TODO: unknown. maybe messages.
// 0A 35 08 00 10 A2 FF 8C F0 03 1A 1B E5 90 8C E6 84 8F E4 BD A0 E7 9A 84 E5 8A A0 E5 A5 BD E5 8F 8B E8 AF B7 E6 B1 82 22 0C E6 BD 9C E6 B1 9F E7 BE A4 E5 8F 8B 28 01
// vProtobuf.loadAs(Msgtype0x210.serializer())
return@lambda528 emptySequence()
},
0x44L to lambda528 { bot ->
val msg = vProtobuf.loadAs(Submsgtype0x44.Submsgtype0x44.MsgBody.serializer())
when {
msg.msgCleanCountMsg != null -> {
}
msg.msgFriendMsgSync != null -> {
}
else -> {
bot.network.logger.debug { "OnlinePush528 0x44L: " + msg._miraiContentToString() }
}
}
return@lambda528 emptySequence()
},
// bot 在其他客户端被踢或主动退出而同步情况
0xD4L to lambda528 { bot ->
@Serializable
data class SubD4(
// ok
val uin: Long
) : ProtoBuf
val uin = vProtobuf.loadAs(SubD4.serializer()).uin
val group = bot.getGroupByUinOrNull(uin) ?: bot.getGroupOrNull(uin)
return@lambda528 if (group != null && bot.groups.delegate.remove(group)) {
sequenceOf(BotLeaveEvent.Active(group))
} else emptySequence()
},
// 群相关, ModFriendRemark, DelFriend, ModGroupProfile
0x27L to lambda528 { bot ->
fun Submsgtype0x27.SubMsgType0x27.ModFriendRemark.transform(bot: QQAndroidBot): Sequence<Packet> {
return this.msgFrdRmk?.asSequence()?.mapNotNull {
val friend = bot.getFriendOrNull(it.fuin) ?: return@mapNotNull null
// TODO: 2020/4/10 ADD REMARK QUERY
FriendRemarkChangeEvent(bot, friend, it.rmkName)
} ?: emptySequence()
}
fun Submsgtype0x27.SubMsgType0x27.DelFriend.transform(bot: QQAndroidBot): Sequence<Packet> {
return this.uint64Uins?.asSequence()?.mapNotNull {
val friend = bot.getFriendOrNull(it) ?: return@mapNotNull null
if (bot.friends.delegate.remove(friend)) {
FriendDeleteEvent(friend)
} else null
} ?: emptySequence()
}
fun Submsgtype0x27.SubMsgType0x27.ModGroupProfile.transform(bot: QQAndroidBot): Sequence<Packet> {
return this.msgGroupProfileInfos?.asSequence()?.mapNotNull { info ->
when (info.field) {
1 -> {
// 群名
val new = info.value.encodeToString()
val group = bot.getGroupOrNull(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.getOrNull(this.cmdUin) ?: return@mapNotNull null
group._name = 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
}
} ?: emptySequence()
}
fun Submsgtype0x27.SubMsgType0x27.ModGroupMemberProfile.transform(bot: QQAndroidBot): Sequence<Packet> {
return this.msgGroupMemberProfileInfos?.asSequence()?.mapNotNull { info ->
when (info.field) {
1 -> { // name card
val new = info.value
val group = bot.getGroupOrNull(this.groupCode) ?: return@mapNotNull null
group.checkIsGroupImpl()
val member = group.getOrNull(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()?.toInt() != 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
}
}
} ?: emptySequence()
}
fun Submsgtype0x27.SubMsgType0x27.ModCustomFace.transform(bot: QQAndroidBot): Sequence<Packet> =
sequenceOf(BotFaceChangedEvent(Bot.getInstance(uin)))
return@lambda528 vProtobuf.loadAs(Submsgtype0x27.SubMsgType0x27.MsgBody.serializer()).msgModInfos.asSequence()
.flatMap {
when {
it.msgModFriendRemark != null -> it.msgModFriendRemark.transform(bot)
it.msgDelFriend != null -> it.msgDelFriend.transform(bot)
it.msgModGroupProfile != null -> it.msgModGroupProfile.transform(bot)
it.msgModGroupMemberProfile != null -> it.msgModGroupMemberProfile.transform(bot)
it.msgModCustomFace != null -> it.msgModCustomFace.transform(bot)
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
}
)
@ExperimentalUnsignedTypes
@OptIn(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Response {
val reqPushMsg = readUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req")
val packets: Sequence<Packet> = reqPushMsg.vMsgInfos.deco(bot.client) { msgInfo ->
when (msgInfo.shMsgType.toInt()) {
732 -> {
val group = bot.getGroup(readUInt().toLong())
GroupImpl.checkIsInstance(group)
val internalType = readByte().toInt()
discardExact(1)
Transformers732[internalType]
?.let { it(this@deco, group, bot) }
?: kotlin.run {
bot.network.logger.debug {
"unknown group 732 type $internalType, data: " + readBytes().toUHexString()
}
return@deco emptySequence()
}
}
// 00 27 1A 0C 1C 2C 3C 4C 5D 00 0C 6D 00 0C 7D 00 0C 8D 00 0C 9C AC BC CC DD 00 0C EC FC 0F 0B 2A 0C 1C 2C 3C 4C 5C 6C 0B 3A 0C 1C 2C 3C 4C 5C 6C 7C 8D 00 0C 9D 00 0C AC BD 00 0C CD 00 0C DC ED 00 0C FC 0F FC 10 0B 4A 0C 1C 2C 3C 4C 5C 6C 7C 8C 96 00 0B 5A 0C 1C 2C 3C 4C 5C 6C 7C 8C 9D 00 0C 0B 6A 0C 1A 0C 1C 26 00 0B 2A 0C 0B 3A 0C 16 00 0B 4A 09 0C 0B 5A 09 0C 0B 0B 7A 0C 1C 2C 36 00 0B 8A 0C 1C 2C 36 00 0B 9A 09 0C 0B AD 00 00 1E 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
528 -> {
val notifyMsgBody = readJceStruct(MsgType0x210.serializer())
Transformers528[notifyMsgBody.uSubMsgType]
?.let { processor -> processor(notifyMsgBody, bot) }
?: kotlin.run {
bot.network.logger.debug {
// Network(1994701021) 16:03:54 : unknown group 528 type 0x0000000000000026, data: 08 01 12 40 0A 06 08 F4 EF BB 8F 04 10 E7 C1 AD B8 02 18 01 22 2C 10 01 1A 1A 18 B4 DC F8 9B 0C 20 E7 C1 AD B8 02 28 06 30 02 A2 01 04 08 93 D6 03 A8 01 08 20 00 28 00 32 08 18 01 20 FE AF AF F5 05 28 00
// VIP 进群提示
"unknown group 528 type 0x${notifyMsgBody.uSubMsgType.toUHexString("")}, data: " + notifyMsgBody.vProtobuf.toUHexString()
}
return@deco emptySequence()
}
}
else -> {
bot.network.logger.debug { "unknown sh type ${msgInfo.shMsgType.toInt()}" }
bot.network.logger.debug { "data=${readBytes().toUHexString()}" }
return@deco emptySequence()
}
}
}
return Response(reqPushMsg, packets)
}
@Suppress("SpellCheckingInspection")
internal data class Response(val request: OnlinePushPack.SvcReqPushMsg, val sequence: Sequence<Packet>) :
MultiPacketBySequence<Packet>(sequence) {
override fun toString(): String {
return "OnlinePush.ReqPush.Response"
}
}
override suspend fun QQAndroidBot.handle(packet: Response, sequenceId: Int): OutgoingPacket? {
return buildResponseUniPacket(client) {
writeJceStruct(
RequestPacket.serializer(),
RequestPacket(
sServantName = "OnlinePush",
sFuncName = "SvcRespPushMsg",
iRequestId = sequenceId,
sBuffer = jceRequestSBuffer(
"resp",
OnlinePushPack.SvcRespPushMsg.serializer(),
OnlinePushPack.SvcRespPushMsg(
packet.request.uin,
packet.request.vMsgInfos.map { msg ->
OnlinePushPack.DelMsgInfo(
fromUin = msg.lFromUin,
shMsgSeq = msg.shMsgSeq,
vMsgCookies = msg.vMsgCookies,
uMsgTime = msg.uMsgTime // captured 0
)
}
)
)
)
)
}
}
}
}

View File

@ -155,7 +155,7 @@ sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
override val messageInternalId: Int,
override val messageTime: Int,
/**
* 撤回操作人, 可能为 [Bot.uin] 或好友的 [User.id]
* 撤回操作人, 可能为 [Bot.id] 或好友的 [User.id]
*/
val operator: Long
) : MessageRecallEvent(), Packet {