1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-25 21:23:55 +08:00

Redesign notice handling and introduce NoticeProcessorPipeline

This commit is contained in:
Him188 2021-06-26 17:14:44 +08:00
parent 497a458be4
commit dc54679acb
30 changed files with 1591 additions and 1086 deletions

View File

@ -14,12 +14,13 @@ import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Stranger
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.utils.MiraiInternalApi
/**
* 新增陌生人的事件
*
*/
public data class StrangerAddEvent internal constructor(
public data class StrangerAddEvent @MiraiInternalApi public constructor(
/**
* 新的陌生人. 已经添加到 [Bot.strangers]
*/

View File

@ -12,4 +12,5 @@
package net.mamoe.mirai.utils
public fun Int.toLongUnsigned(): Long = this.toLong().and(0xFFFF_FFFF)
public fun Int.toLongUnsigned(): Long = this.toLong().and(0xFFFF_FFFF)
public fun Short.toIntUnsigned(): Int = this.toUShort().toInt()

View File

@ -13,6 +13,7 @@
package net.mamoe.mirai.utils
import java.util.*
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.reflect.KClass
@ -238,4 +239,9 @@ public fun String.truncated(length: Int, truncated: String = "..."): String {
return if (this.length > length) {
this.take(10) + truncated
} else this
}
public inline fun <T> T.context(block: T.() -> Unit) {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return block()
}

View File

@ -335,7 +335,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
}
@LowLevelApi
override fun newFriend(bot: Bot, friendInfo: FriendInfo): Friend {
override fun newFriend(bot: Bot, friendInfo: FriendInfo): FriendImpl {
return FriendImpl(
bot.asQQAndroidBot(),
bot.coroutineContext + SupervisorJob(bot.supervisorJob),
@ -344,7 +344,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
}
@LowLevelApi
override fun newStranger(bot: Bot, strangerInfo: StrangerInfo): Stranger {
override fun newStranger(bot: Bot, strangerInfo: StrangerInfo): StrangerImpl {
return StrangerImpl(
bot.asQQAndroidBot(),
bot.coroutineContext + SupervisorJob(bot.supervisorJob),

View File

@ -14,12 +14,9 @@ import kotlinx.atomicfu.atomic
import kotlinx.coroutines.isActive
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.Bot
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.BotOnlineEvent
import net.mamoe.mirai.event.events.BotReloginEvent
import net.mamoe.mirai.internal.contact.checkIsGroupImpl
import net.mamoe.mirai.internal.network.component.ComponentStorage
import net.mamoe.mirai.internal.network.component.ComponentStorageDelegate
import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
@ -152,6 +149,7 @@ internal open class QQAndroidBot constructor(
// There's no need to interrupt a broadcasting event when network handler closed.
set(EventDispatcher, EventDispatcherImpl(bot.coroutineContext, logger.subLogger("EventDispatcher")))
set(NoticeProcessorPipeline, NoticeProcessorPipelineImpl(networkLogger.subLogger("NoticeProcessorPipeline")))
set(SsoProcessorContext, SsoProcessorContextImpl(bot))
set(SsoProcessor, SsoProcessorImpl(get(SsoProcessorContext)))
@ -238,14 +236,9 @@ internal open class QQAndroidBot constructor(
///////////////////////////////////////////////////////////////////////////
override lateinit var nick: String
// internally visible only
fun getGroupByUin(uin: Long): Group {
return getGroupByUinOrNull(uin)
?: throw NoSuchElementException("Group ${Mirai.calculateGroupCodeByGroupUin(uin)} not found")
}
fun getGroupByUinOrNull(uin: Long): Group? {
return groups.firstOrNull { it.checkIsGroupImpl(); it.uin == uin }
}
}
internal fun QQAndroidBot.getGroupByUinOrFail(uin: Long) =
getGroupByUin(uin) ?: throw NoSuchElementException("group.uin=$uin")
internal fun QQAndroidBot.getGroupByUin(uin: Long) = groups.firstOrNull { it.uin == uin }

View File

@ -21,8 +21,13 @@ internal class AnonymousMemberImpl(
group: GroupImpl,
coroutineContext: CoroutineContext,
memberInfo: MemberInfo,
override val anonymousId: String,
) : AnonymousMember, AbstractMember(group, coroutineContext, memberInfo) {
init {
requireNotNull(memberInfo.anonymousId) { "anonymousId must not be null" }
}
override val anonymousId: String get() = info.anonymousId!!
override suspend fun mute(durationSeconds: Int) {
checkBotPermissionHigherThanThis("mute")
getMiraiImpl().muteAnonymousMember(bot, anonymousId, nameCard, group.uin, durationSeconds)

View File

@ -130,9 +130,7 @@ internal class GroupImpl(
}
override operator fun get(id: Long): NormalMemberImpl? {
if (id == bot.id) {
return botAsMember
}
if (id == bot.id) return botAsMember
return members.firstOrNull { it.id == id }
}
@ -292,10 +290,10 @@ internal class GroupImpl(
@Deprecated("use addNewNormalMember or newAnonymousMember")
internal fun Group.newMember(memberInfo: MemberInfo): Member {
this.checkIsGroupImpl()
memberInfo.anonymousId?.let { anId ->
memberInfo.anonymousId?.let {
return AnonymousMemberImpl(
this, this.coroutineContext,
memberInfo, anId
memberInfo
)
}
return NormalMemberImpl(
@ -322,25 +320,25 @@ internal fun Group.newNormalMember(memberInfo: MemberInfo): NormalMemberImpl {
internal fun Group.newAnonymousMember(memberInfo: MemberInfo): AnonymousMemberImpl? {
this.checkIsGroupImpl()
memberInfo.anonymousId?.let { anId ->
return AnonymousMemberImpl(
this, this.coroutineContext,
memberInfo, anId
)
memberInfo.anonymousId?.let {
return AnonymousMemberImpl(this, this.coroutineContext, memberInfo)
}
return null
}
internal fun GroupImpl.newAnonymous(name: String, id: String): AnonymousMemberImpl = newMember(
MemberInfoImpl(
uin = 80000000L,
nick = name,
permission = MemberPermission.MEMBER,
remark = "匿名",
nameCard = name,
specialTitle = "匿名",
muteTimestamp = 0,
anonymousId = id,
internal fun GroupImpl.newAnonymous(name: String, id: String): AnonymousMemberImpl {
return AnonymousMemberImpl(
this, this.coroutineContext,
MemberInfoImpl(
uin = 80000000L,
nick = name,
permission = MemberPermission.MEMBER,
remark = "匿名",
nameCard = name,
specialTitle = "匿名",
muteTimestamp = 0,
anonymousId = id,
)
)
) as AnonymousMemberImpl
}

View File

@ -23,6 +23,7 @@ import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.components.MessageSvcSyncer
import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.notice.GroupMessageProcessor.SendGroupMessageReceipt
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
@ -391,9 +392,8 @@ internal open class GroupSendMessageHandler(
fromAppId: Int,
): OnlineMessageSource.Outgoing {
val receipt: OnlinePushPbPushGroupMsg.SendGroupMessageReceipt =
nextEventOrNull(3000) { it.fromAppId == fromAppId }
?: OnlinePushPbPushGroupMsg.SendGroupMessageReceipt.EMPTY
val receipt: SendGroupMessageReceipt =
nextEventOrNull(3000) { it.fromAppId == fromAppId } ?: SendGroupMessageReceipt.EMPTY
return OnlineMessageSourceToGroupImpl(
contact,
@ -442,7 +442,7 @@ internal open class GroupSendMessageHandler(
groupCode = id,
md5 = image.md5,
size = if (image is OnlineFriendImageImpl) image.delegate.fileLen else 0
).sendAndExpect<ImgStore.GroupPicUp.Response>()
).sendAndExpect()
return OfflineGroupImage(image.imageId).also { img ->
when (response) {
is ImgStore.GroupPicUp.Response.FileExists -> {

View File

@ -21,11 +21,11 @@ internal data class MemberInfoImpl(
override val uin: Long,
override var nick: String,
override var permission: MemberPermission,
override var remark: String,
override val nameCard: String,
override val specialTitle: String,
override val muteTimestamp: Int,
override val anonymousId: String?,
override var remark: String = "",
override val nameCard: String = "",
override val specialTitle: String = "",
override val muteTimestamp: Int = 0,
override val anonymousId: String? = null,
override val joinTimestamp: Int = currentTimeSeconds().toInt(),
override var lastSpeakTimestamp: Int = 0,
override val isOfficialBot: Boolean = false,

View File

@ -20,10 +20,10 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.asyncFromEventOrNull
import net.mamoe.mirai.internal.network.notice.GroupMessageProcessor.SendGroupMessageReceipt
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.data.proto.SourceMsg
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushPbPushGroupMsg.SendGroupMessageReceipt
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource

View File

@ -32,20 +32,29 @@ interface Packet {
/**
* PacketFactory 可以一次解析多个包出来. 它们将会被分别广播.
*/
internal interface MultiPacket<out P : Packet> : Packet, Iterable<P>
internal interface MultiPacket : Packet, Collection<Packet>
internal open class MultiPacketByIterable<out P : Packet>(internal val delegate: Iterable<P>) : MultiPacket<P>,
Iterable<P> by delegate {
override fun toString(): String = "MultiPacketByIterable"
internal fun Collection<Packet>.toPacket(): Packet {
return when (this.size) {
1 -> this.single()
else -> MultiPacketImpl(this)
}
}
internal open class MultiPacketBySequence<out P : Packet>(internal val delegate: Sequence<P>) :
MultiPacket<P> {
override operator fun iterator(): Iterator<P> = delegate.iterator()
internal fun MultiPacket(delegate: Collection<Packet>): MultiPacket = MultiPacketImpl(delegate)
override fun toString(): String = "MultiPacketBySequence"
internal open class MultiPacketImpl(
val delegate: Collection<Packet>
) : MultiPacket, Collection<Packet> by delegate {
override fun toString(): String = delegate.joinToString(
separator = "\n",
prefix = "MultiPacket [\n",
postfix = "]",
)
}
internal class ParseErrorPacket(
val error: Throwable,
val direction: Direction = Direction.TO_BOT_LOGGER,

View File

@ -0,0 +1,196 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.components
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.component.ComponentKey
import net.mamoe.mirai.internal.network.component.ComponentStorage
import net.mamoe.mirai.internal.network.notice.decoders.MsgType0x2DC
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgInfo
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush
import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMsgInfo
import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetMsg
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushPbPushTransMsg
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.TypeSafeMap
import net.mamoe.mirai.utils.uncheckedCast
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
import kotlin.reflect.KClass
internal interface PipelineContext {
val bot: QQAndroidBot
val attributes: TypeSafeMap
val isConsumed: Boolean
/**
* Mark the input as consumed so that there will not be warnings like 'Unknown type xxx'
*
* If this is executed, make sure you provided all information important for debugging.
*
* You need to invoke [markAsConsumed] if your implementation includes some `else` branch which covers all situations,
* and throws a [contextualBugReportException] or logs something.
*/
fun markAsConsumed()
val collected: Collection<Packet>
// DSL to simplify some expressions
operator fun Collection<Packet>.plusAssign(packet: Packet) {
require(this === collected) { "`plusAssign` can only be applied to `collected`" }
collect(packet)
}
/**
* Collect a result.
*/
fun collect(packet: Packet)
/**
* Collect results.
*/
fun collect(packets: Iterable<Packet>)
/**
* Fire the [data] into the processor pipeline.
*
* @return result collected from processors. This would also have been collected to this context (where you call [fire]).
*/
suspend fun fire(data: Any?): Collection<Packet>
}
internal inline val PipelineContext.context get() = this
/**
* Centralized processor pipeline for [MessageSvcPbGetMsg] and [OnlinePushPbPushTransMsg]
*/
internal interface NoticeProcessorPipeline {
fun registerProcessor(processor: NoticeProcessor)
suspend fun process(bot: QQAndroidBot, data: Any?, attributes: TypeSafeMap = TypeSafeMap()): Collection<Packet>
companion object : ComponentKey<NoticeProcessorPipeline> {
val ComponentStorage.noticeProcessorPipeline get() = get(NoticeProcessorPipeline)
}
}
internal class NoticeProcessorPipelineImpl(
private val logger: MiraiLogger,
) : NoticeProcessorPipeline {
private val processors = ArrayList<NoticeProcessor>()
private val processorsLock = ReentrantReadWriteLock()
override fun registerProcessor(processor: NoticeProcessor) {
processorsLock.write {
processors.add(processor)
}
}
inner class ContextImpl(
override val bot: QQAndroidBot, override val attributes: TypeSafeMap,
) : PipelineContext {
override var isConsumed: Boolean = false
override fun markAsConsumed() {
isConsumed = true
}
override val collected = ConcurrentLinkedQueue<Packet>()
override fun collect(packet: Packet) {
collected.add(packet)
}
override fun collect(packets: Iterable<Packet>) {
this.collected.addAll(packets)
}
override suspend fun fire(data: Any?): Collection<Packet> {
return process(bot, data, attributes)
}
}
override suspend fun process(bot: QQAndroidBot, data: Any?, attributes: TypeSafeMap): Collection<Packet> {
processorsLock.read {
val context = ContextImpl(bot, attributes)
for (processor in processors) {
processor.process(context, data)
}
return context.collected
}
}
}
/**
* A processor handling some specific type of message.
*/
internal interface NoticeProcessor {
suspend fun process(context: PipelineContext, data: Any?)
}
internal abstract class AnyNoticeProcessor : SimpleNoticeProcessor<Any>(type())
internal abstract class SimpleNoticeProcessor<T : Any>(
private val type: KClass<T>,
) : NoticeProcessor {
final override suspend fun process(context: PipelineContext, data: Any?) {
if (type.isInstance(data)) {
context.process0(data.uncheckedCast())
}
}
protected abstract suspend fun PipelineContext.process0(data: T)
companion object {
@JvmStatic
protected inline fun <reified T : Any> type(): KClass<T> = T::class
}
}
internal abstract class MsgCommonMsgProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type()) {
abstract override suspend fun PipelineContext.process0(data: MsgComm.Msg)
}
internal abstract class MixedNoticeProcessor : AnyNoticeProcessor() {
final override suspend fun PipelineContext.process0(data: Any) {
when (data) {
is MsgInfo -> processImpl(data)
is PbMsgInfo -> processImpl(data)
is MsgOnlinePush.PbPushMsg -> processImpl(data)
is MsgComm.Msg -> processImpl(data)
is MsgType0x210 -> processImpl(data)
is MsgType0x2DC -> processImpl(data)
is Structmsg.StructMsg -> processImpl(data)
}
}
protected open suspend fun PipelineContext.processImpl(data: MsgInfo) {}
protected open suspend fun PipelineContext.processImpl(data: MsgType0x210) {}
protected open suspend fun PipelineContext.processImpl(data: MsgType0x2DC) {}
protected open suspend fun PipelineContext.processImpl(data: PbMsgInfo) {}
protected open suspend fun PipelineContext.processImpl(data: MsgOnlinePush.PbPushMsg) {}
protected open suspend fun PipelineContext.processImpl(data: MsgComm.Msg) {}
protected open suspend fun PipelineContext.processImpl(data: Structmsg.StructMsg) {}
}

View File

@ -75,7 +75,7 @@ internal class EventBroadcasterPacketHandler(
private fun impl(packet: Packet?) {
if (packet == null) return
if (packet is MultiPacket<*>) {
if (packet is MultiPacket) {
for (p in packet) {
impl(p)
}

View File

@ -57,7 +57,7 @@ internal class PacketLoggingStrategyImpl(
packet.direction.getLogger(bot).error("Exception in parsing packet.", packet.error)
}
if (packet is MultiPacket<*>) {
if (packet is MultiPacket) {
for (d in packet) {
logReceivedImpl(d, incomingPacket, logger)
}

View File

@ -0,0 +1,105 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.notice
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor
import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMsgInfo
import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.utils.read
internal class BinaryMessageProcessor : SimpleNoticeProcessor<PbMsgInfo>(type()), GroupEventProcessorContext {
override suspend fun PipelineContext.process0(data: PbMsgInfo) {
data.msgData.read<Unit> {
when (data.msgType) {
44 -> {
// 3D C4 33 DD 01 FF CD 76 F4 03 C3 7E 2E 34
// 群转让
// start with 3D C4 33 DD 01 FF
// 3D C4 33 DD 01 FF C3 7E 2E 34 CD 76 F4 03
// 权限变更
// 3D C4 33 DD 01 00/01 .....
// 3D C4 33 DD 01 01 C3 7E 2E 34 01
this.discardExact(5)
when (val mode = readUByte().toInt()) {
0xFF -> {
TODO("removed")
}
else -> {
TODO("removed")
}
}
}
34 -> {
TODO("removed")
}
else -> {
when {
data.msgType == 529 && data.msgSubtype == 9 -> {
/*
PbMsgInfo#1773430973 {
fromUin=0x0000000026BA1173(649728371)
generalFlag=0x00000001(1)
msgData=0A 07 70 72 69 6E 74 65 72 10 02 1A CD 02 0A 1F 53 61 6D 73 75 6E 67 20 4D 4C 2D 31 38 36 30 20 53 65 72 69 65 73 20 28 55 53 42 30 30 31 29 0A 16 4F 6E 65 4E 6F 74 65 20 66 6F 72 20 57 69 6E 64 6F 77 73 20 31 30 0A 19 50 68 61 6E 74 6F 6D 20 50 72 69 6E 74 20 74 6F 20 45 76 65 72 6E 6F 74 65 0A 11 4F 6E 65 4E 6F 74 65 20 28 44 65 73 6B 74 6F 70 29 0A 1D 4D 69 63 72 6F 73 6F 66 74 20 58 50 53 20 44 6F 63 75 6D 65 6E 74 20 57 72 69 74 65 72 0A 16 4D 69 63 72 6F 73 6F 66 74 20 50 72 69 6E 74 20 74 6F 20 50 44 46 0A 15 46 6F 78 69 74 20 50 68 61 6E 74 6F 6D 20 50 72 69 6E 74 65 72 0A 03 46 61 78 32 09 0A 03 6A 70 67 10 01 18 00 32 0A 0A 04 6A 70 65 67 10 01 18 00 32 09 0A 03 70 6E 67 10 01 18 00 32 09 0A 03 67 69 66 10 01 18 00 32 09 0A 03 62 6D 70 10 01 18 00 32 09 0A 03 64 6F 63 10 01 18 01 32 0A 0A 04 64 6F 63 78 10 01 18 01 32 09 0A 03 74 78 74 10 00 18 00 32 09 0A 03 70 64 66 10 01 18 01 32 09 0A 03 70 70 74 10 01 18 01 32 0A 0A 04 70 70 74 78 10 01 18 01 32 09 0A 03 78 6C 73 10 01 18 01 32 0A 0A 04 78 6C 73 78 10 01 18 01
msgSeq=0x00001AFF(6911)
msgSubtype=0x00000009(9)
msgTime=0x5FDF21A3(1608458659)
msgType=0x00000211(529)
msgUid=0x010000005FDEE04C(72057595646369868)
realMsgTime=0x5FDF21A3(1608458659)
svrIp=0x3E689409(1047041033)
toUin=0x0000000026BA1173(649728371)
}
*/
/*
*
printer
Samsung ML-1860 Series (USB001)
OneNote for Windows 10
Phantom Print to Evernote
OneNote (Desktop)
Microsoft XPS Document Writer
Microsoft Print to PDF
Foxit Phantom Printer
Fax2
jpg2
jpeg2
png2
gif2
bmp2
doc2
docx2
txt2
pdf2
ppt2
pptx2
xls2
xlsx*/
return
}
}
throw contextualBugReportException(
"解析 OnlinePush.PbPushTransMsg, msgType=${data.msgType}",
data._miraiContentToString(),
null,
"并描述此时机器人是否被踢出, 或是否有成员列表变更等动作."
)
}
}
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.notice
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.GroupImpl
import net.mamoe.mirai.internal.contact.info.GroupInfoImpl
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.getGroupByUin
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
internal interface GroupEventProcessorContext {
fun MsgComm.Msg.getNewMemberInfo(): MemberInfoImpl {
return MemberInfoImpl(
nameCard = msgHead.authNick.ifEmpty { msgHead.fromNick },
permission = MemberPermission.MEMBER,
specialTitle = "",
muteTimestamp = 0,
uin = msgHead.authUin,
nick = msgHead.authNick.ifEmpty { msgHead.fromNick },
remark = "",
anonymousId = null
)
}
suspend fun QQAndroidBot.createGroupForBot(groupUin: Long): GroupImpl? {
val group = getGroupByUin(groupUin)
if (group != null) {
return null
}
return getNewGroup(Mirai.calculateGroupCodeByGroupUin(groupUin))?.apply { groups.delegate.add(this) }
}
suspend fun QQAndroidBot.getNewGroup(groupCode: Long): GroupImpl? {
val troopNum = FriendList.GetTroopListSimplify(client)
.sendAndExpect(network, timeoutMillis = 10_000, retry = 5)
.groups.firstOrNull { it.groupCode == groupCode } ?: return null
return GroupImpl(
bot = this,
coroutineContext = coroutineContext,
id = groupCode,
groupInfo = GroupInfoImpl(troopNum),
members = Mirai.getRawGroupMemberList(
this,
troopNum.groupUin,
troopNum.groupCode,
troopNum.dwGroupOwnerUin
)
)
}
}

View File

@ -0,0 +1,443 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.notice
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.cancel
import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.MemberPermission.*
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.internal.contact.addNewNormalMember
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.getGroupByUin
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.network.components.ContactUpdater
import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.notice.decoders.MsgType0x2DC
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210
import net.mamoe.mirai.internal.network.protocol.data.proto.*
import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.parseToMessageDataList
import net.mamoe.mirai.internal.utils.toMemberInfo
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.context
import net.mamoe.mirai.utils.debug
import net.mamoe.mirai.utils.read
/**
* @see BotJoinGroupEvent
* @see MemberJoinEvent
*
* @see BotLeaveEvent
* @see MemberLeaveEvent
*
* @see MemberPermissionChangeEvent
* @see BotGroupPermissionChangeEvent
*/
internal class GroupListNoticeProcessor(
private val logger: MiraiLogger
) : MixedNoticeProcessor(), GroupEventProcessorContext {
override suspend fun PipelineContext.processImpl(data: MsgType0x210) {
if (data.uSubMsgType != 0x44L) return
val msg = data.vProtobuf.loadAs(Submsgtype0x44.Submsgtype0x44.MsgBody.serializer())
if (msg.msgGroupMsgSync == null) return
when (msg.msgGroupMsgSync.msgType) {
1, 2 -> {
bot.components[ContactUpdater].groupListModifyLock.withLock {
bot.createGroupForBot(Mirai.calculateGroupUinByGroupCode(msg.msgGroupMsgSync.grpCode))?.let {
collect(BotJoinGroupEvent.Active(it))
}
}
}
}
}
/**
* @see MemberJoinEvent.Invite
* @see MemberLeaveEvent.Quit
*/
override suspend fun PipelineContext.processImpl(data: MsgType0x2DC) = data.context {
if (data.kind != 0x10) return
val proto = data.buf.loadAs(TroopTips0x857.NotifyMsgBody.serializer(), offset = 1)
if (proto.optEnumType != 1) return
val tipsInfo = proto.optMsgGraytips ?: return
val message = tipsInfo.optBytesContent.decodeToString()
// 机器人信息
when (tipsInfo.robotGroupOpt) {
// 添加
1 -> {
val dataList = message.parseToMessageDataList()
val invitor = dataList.first().let { messageData ->
group.getOrFail(messageData.data.toLong())
}
val member = dataList.last().let { messageData ->
group.addNewNormalMember(messageData.toMemberInfo())
}
collect(MemberJoinEvent.Invite(member, invitor))
}
// 移除
2 -> {
message.parseToMessageDataList().first().let {
val member = group.getOrFail(it.data.toLong())
group.members.delegate.remove(member)
collect(MemberLeaveEvent.Quit(member))
}
}
else -> {
logger.debug { "Unknown robotGroupOpt ${tipsInfo.robotGroupOpt}, message=$message" }
}
}
return markAsConsumed()
}
/**
* @see MemberJoinEvent.Invite
* @see BotJoinGroupEvent.Invite
* @see MemberJoinEvent.Active
* @see BotJoinGroupEvent.Active
*/
override suspend fun PipelineContext.processImpl(data: MsgComm.Msg) = data.context {
if (data.msgHead.msgType != 33) return
bot.components[ContactUpdater].groupListModifyLock.withLock {
msgBody.msgContent.read {
val groupUin = Mirai.calculateGroupUinByGroupCode(readUInt().toLong())
val group =
bot.getGroupByUin(groupUin) ?: bot.createGroupForBot(groupUin) ?: return markAsConsumed()
discardExact(1)
val joinedMemberUin = readUInt().toLong()
val joinType = readByte().toInt()
val invitorUin = readUInt().toLong()
when (joinType) {
// 邀请加入
-125, 3 -> {
val invitor = group[invitorUin] ?: return markAsConsumed()
collected += if (joinedMemberUin == bot.id) {
BotJoinGroupEvent.Invite(invitor)
} else {
MemberJoinEvent.Invite(group.addNewNormalMember(getNewMemberInfo()), invitor)
}
}
// 通过群员分享的二维码/直接加入
-126, 2 -> {
collected += if (joinedMemberUin == bot.id) {
BotJoinGroupEvent.Active(group)
} else {
MemberJoinEvent.Active(group.addNewNormalMember(getNewMemberInfo()))
}
}
// 忽略
else -> {
}
}
}
// 邀请入群
// package: 27 0B 60 E7 01 CA CC 69 8B 83 44 71 47 90 06 B9 DC C0 ED D4 B1 00 30 33 44 30 42 38 46 30 39 37 32 38 35 43 34 31 38 30 33 36 41 34 36 31 36 31 35 32 37 38 46 46 43 30 41 38 30 36 30 36 45 38 31 43 39 41 34 38 37
// package: groupUin + 01 CA CC 69 8B 83 + invitorUin + length(06) + string + magicKey
// 主动入群, 直接加入: msgContent=27 0B 60 E7 01 76 E4 B8 DD 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 42 39 41 30 33 45 38 34 30 39 34 42 46 30 45 32 45 38 42 31 43 43 41 34 32 42 38 42 44 42 35 34 44 42 31 44 32 32 30 46 30 38 39 46 46 35 41 38
// 主动直接加入 27 0B 60 E7 01 76 E4 B8 DD 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 33 30 45 38 42 31 33 46 41 41 31 33 46 38 31 35 34 41 38 33 32 37 31 43 34 34 38 35 33 35 46 45 31 38 32 43 39 42 43 46 46 32 44 39 39 46 41 37
// 有人被邀请(经过同意后)加入 27 0B 60 E7 01 76 E4 B8 DD 83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 34 30 34 38 32 33 38 35 37 41 37 38 46 33 45 37 35 38 42 39 38 46 43 45 44 43 32 41 30 31 36 36 30 34 31 36 39 35 39 30 38 39 30 39 45 31 34 34
// 搜索到群, 直接加入 27 0B 60 E7 01 07 6E 47 BA 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 32 30 39 39 42 39 41 46 32 39 41 35 42 33 46 34 32 30 44 36 44 36 39 35 44 38 45 34 35 30 46 30 45 30 38 45 31 41 39 42 46 46 45 32 30 32 34 35
}
}
///////////////////////////////////////////////////////////////////////////
// Structmsg.StructMsg
///////////////////////////////////////////////////////////////////////////
override suspend fun PipelineContext.processImpl(data: Structmsg.StructMsg) = data.msg.context {
markAsConsumed()
if (this == null) return
when (subType) {
// 处理被邀请入群 或 处理成员入群申请
1 -> when (groupMsgType) {
1 -> {
// 成员申请入群
MemberJoinRequestEvent(
bot, data.msgSeq, msgAdditional,
data.reqUin, groupCode, groupName, reqUinNick
)
}
2 -> {
// Bot 被邀请入群
BotInvitedJoinGroupRequestEvent(
bot, data.msgSeq, actionUin,
groupCode, groupName, actionUinNick
)
}
22 -> {
// 成员邀请入群
MemberJoinRequestEvent(
bot, data.msgSeq, msgAdditional,
data.reqUin, groupCode, groupName, reqUinNick, actionUin
)
}
else -> throw contextualBugReportException(
"parse SystemMsgNewGroup, subType=1",
this._miraiContentToString(),
additional = "并尽量描述此时机器人是否正被邀请加入群, 或者是有有新群员加入此群"
)
}
2 -> { // 被邀请入群, 自动同意, 不需处理
// val group = bot.getNewGroup(groupCode) ?: return null
// val invitor = group[actionUin]
//
// BotJoinGroupEvent.Invite(invitor)
}
3 -> { // 已被请他管理员处理
}
5 -> {
val group = bot.getGroup(groupCode) ?: return
when (groupMsgType) {
3 -> {
// https://github.com/mamoe/mirai/issues/651
// msgDescribe=将你设置为管理员
// msgTitle=管理员设置
}
13 -> {
// 成员主动退出, 机器人是管理员, 接到通知
// 但无法获取是哪个成员.
}
7 -> { // 机器人被踢
val operator = group[actionUin] ?: return
BotLeaveEvent.Kick(operator)
}
else -> {
throw contextualBugReportException(
"解析 NewContact.SystemMsgNewGroup, subType=5, groupMsgType=$groupMsgType",
this._miraiContentToString(),
null,
"并描述此时机器人是否被踢出群等"
)
}
}
}
else -> throw contextualBugReportException(
"解析 NewContact.SystemMsgNewGroup, subType=$subType, groupMsgType=$groupMsgType",
forDebug = this._miraiContentToString(),
additional = "并尽量描述此时机器人是否正被邀请加入群, 或者是有有新群员加入此群"
)
}
}
///////////////////////////////////////////////////////////////////////////
// OnlinePushTrans.PbMsgInfo
///////////////////////////////////////////////////////////////////////////
override suspend fun PipelineContext.processImpl(data: OnlinePushTrans.PbMsgInfo) {
markAsConsumed()
when (data.msgType) {
44 -> data.msgData.read {
discardExact(5)
val kind = readUByte().toInt()
if (kind == 0xFF) {
val from = readUInt().toLong()
val to = readUInt().toLong()
handleGroupOwnershipTransfer(data, from, to)
} else {
val var5 = if (kind == 0 || kind == 1) 0 else readUInt().toInt()
val target = readUInt().toLong()
if (var5 == 0) {
val newPermission = if (remaining == 1L) readByte() else return
handlePermissionChange(data, target, newPermission.toInt())
}
}
}
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
*/
data.msgData.read {
readUInt().toLong() // groupCode
readByte().toInt() // follow type
val target = readUInt().toLong()
val kind = readUByte().toInt()
val operator = readUInt().toLong()
val groupUin = data.fromUin
handleLeave(target, kind, operator, groupUin)
}
}
else -> {
when {
data.msgType == 529 && data.msgSubtype == 9 -> {
/*
PbMsgInfo#1773430973 {
fromUin=0x0000000026BA1173(649728371)
generalFlag=0x00000001(1)
msgData=0A 07 70 72 69 6E 74 65 72 10 02 1A CD 02 0A 1F 53 61 6D 73 75 6E 67 20 4D 4C 2D 31 38 36 30 20 53 65 72 69 65 73 20 28 55 53 42 30 30 31 29 0A 16 4F 6E 65 4E 6F 74 65 20 66 6F 72 20 57 69 6E 64 6F 77 73 20 31 30 0A 19 50 68 61 6E 74 6F 6D 20 50 72 69 6E 74 20 74 6F 20 45 76 65 72 6E 6F 74 65 0A 11 4F 6E 65 4E 6F 74 65 20 28 44 65 73 6B 74 6F 70 29 0A 1D 4D 69 63 72 6F 73 6F 66 74 20 58 50 53 20 44 6F 63 75 6D 65 6E 74 20 57 72 69 74 65 72 0A 16 4D 69 63 72 6F 73 6F 66 74 20 50 72 69 6E 74 20 74 6F 20 50 44 46 0A 15 46 6F 78 69 74 20 50 68 61 6E 74 6F 6D 20 50 72 69 6E 74 65 72 0A 03 46 61 78 32 09 0A 03 6A 70 67 10 01 18 00 32 0A 0A 04 6A 70 65 67 10 01 18 00 32 09 0A 03 70 6E 67 10 01 18 00 32 09 0A 03 67 69 66 10 01 18 00 32 09 0A 03 62 6D 70 10 01 18 00 32 09 0A 03 64 6F 63 10 01 18 01 32 0A 0A 04 64 6F 63 78 10 01 18 01 32 09 0A 03 74 78 74 10 00 18 00 32 09 0A 03 70 64 66 10 01 18 01 32 09 0A 03 70 70 74 10 01 18 01 32 0A 0A 04 70 70 74 78 10 01 18 01 32 09 0A 03 78 6C 73 10 01 18 01 32 0A 0A 04 78 6C 73 78 10 01 18 01
msgSeq=0x00001AFF(6911)
msgSubtype=0x00000009(9)
msgTime=0x5FDF21A3(1608458659)
msgType=0x00000211(529)
msgUid=0x010000005FDEE04C(72057595646369868)
realMsgTime=0x5FDF21A3(1608458659)
svrIp=0x3E689409(1047041033)
toUin=0x0000000026BA1173(649728371)
}
*/
return
}
}
throw contextualBugReportException(
"解析 OnlinePush.PbPushTransMsg, msgType=${data.msgType}",
data._miraiContentToString(),
null,
"并描述此时机器人是否被踢出, 或是否有成员列表变更等动作."
)
}
}
}
private fun PipelineContext.handleLeave(
target: Long,
kind: Int,
operator: Long,
groupUin: Long
) {
when (kind) {
2, 0x82 -> bot.getGroupByUin(groupUin)?.let { group ->
if (target == bot.id) {
collect(BotLeaveEvent.Active(group))
bot.groups.delegate.remove(group)
group.cancel(CancellationException("Left actively"))
} else {
val member = group[target] ?: return
collect(MemberLeaveEvent.Quit(member))
group.members.delegate.remove(member)
member.cancel(CancellationException("Left actively"))
}
}
3, 0x83 -> bot.getGroupByUin(groupUin)?.let { group ->
if (target == bot.id) {
val member = group.members[operator] ?: return
collect(BotLeaveEvent.Kick(member))
bot.groups.delegate.remove(group)
group.cancel(CancellationException("Being kicked"))
} else {
val member = group[target] ?: return
collect(MemberLeaveEvent.Kick(member, group.members[operator]))
group.members.delegate.remove(member)
member.cancel(CancellationException("Being kicked"))
}
}
}
}
/**
* Group owner changes permission of a member, when bot is a member.
*
* @see BotGroupPermissionChangeEvent
* @see MemberPermissionChangeEvent
*/
private fun PipelineContext.handlePermissionChange(
data: OnlinePushTrans.PbMsgInfo,
target: Long,
newPermissionByte: Int
) {
val group = bot.getGroupByUin(data.fromUin) ?: return
val newPermission = if (newPermissionByte == 1) ADMINISTRATOR else MEMBER
if (target == bot.id) {
if (group.botPermission == newPermission) return
collect(BotGroupPermissionChangeEvent(group, group.botPermission, newPermission))
group.botAsMember.permission = newPermission
} else {
val member = group[target] ?: return
if (member.permission == newPermission) return
collect(MemberPermissionChangeEvent(member, member.permission, newPermission))
member.permission = newPermission
}
}
/**
* Owner of the group [from] transfers ownership to another member [to], or retrieve ownership.
*/
// TODO: 2021/6/26 tests
private suspend fun PipelineContext.handleGroupOwnershipTransfer(
data: OnlinePushTrans.PbMsgInfo,
from: Long,
to: Long,
) {
val group = bot.getGroupByUin(data.fromUin)
if (from == bot.id) {
// bot -> member
group ?: return markAsConsumed()
// Bot permission changed to MEMBER
if (group.botPermission != MEMBER) {
collect(BotGroupPermissionChangeEvent(group, group.botPermission, MEMBER))
group.botAsMember.permission = MEMBER
}
// member Retrieve or permission changed to OWNER
var newOwner = group[to]
if (newOwner == null) {
newOwner = group.addNewNormalMember(MemberInfoImpl(uin = to, nick = "", permission = OWNER))
collect(MemberJoinEvent.Retrieve(newOwner))
} else if (newOwner.permission != OWNER) {
collect(MemberPermissionChangeEvent(newOwner, newOwner.permission, OWNER))
newOwner.permission = OWNER
}
} else {
// member -> bot
// bot Retrieve or permission changed to OWNER
if (group == null) {
collect(BotJoinGroupEvent.Retrieve(bot.createGroupForBot(data.fromUin)!!))
return
}
// member permission changed to MEMBER
val member = group[from]
if (member != null && member.permission != MEMBER) {
collect(MemberPermissionChangeEvent(member, member.permission, MEMBER))
member.permission = MEMBER
} else {
// if member is null, he has already quit the group in another event.
}
if (group.botPermission != OWNER) {
collect(BotGroupPermissionChangeEvent(group, group.botPermission, OWNER))
group.botAsMember.permission = OWNER
}
}
}
}

View File

@ -0,0 +1,197 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.notice
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.GroupMessageEvent
import net.mamoe.mirai.event.events.GroupMessageSyncEvent
import net.mamoe.mirai.event.events.MemberCardChangeEvent
import net.mamoe.mirai.internal.contact.GroupImpl
import net.mamoe.mirai.internal.contact.NormalMemberImpl
import net.mamoe.mirai.internal.contact.info
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.contact.newAnonymous
import net.mamoe.mirai.internal.message.toMessageChainOnline
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor
import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.notice.GroupMessageProcessor.MemberNick.Companion.generateMemberNickFromMember
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush
import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x8fc
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.utils.*
internal class GroupMessageProcessor : SimpleNoticeProcessor<MsgOnlinePush.PbPushMsg>(type()) {
internal data class SendGroupMessageReceipt(
val messageRandom: Int,
val sequenceId: Int,
val fromAppId: Int,
) : Packet, Event, Packet.NoLog, AbstractEvent() {
override fun toString(): String {
return "OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt(messageRandom=$messageRandom, sequenceId=$sequenceId)"
}
companion object {
val EMPTY = SendGroupMessageReceipt(0, 0, 0)
}
}
private data class MemberNick(val nick: String, val isNameCard: Boolean = false) {
companion object {
fun Member.generateMemberNickFromMember(): MemberNick {
return nameCard.takeIf { nameCard.isNotEmpty() }?.let {
MemberNick(nameCard, true)
} ?: MemberNick(nick, false)
}
}
}
override suspend fun PipelineContext.process0(data: MsgOnlinePush.PbPushMsg) {
val msgHead = data.msg.msgHead
val isFromSelfAccount = msgHead.fromUin == bot.id
if (isFromSelfAccount) {
val messageRandom = data.msg.msgBody.richText.attr?.random ?: return
if (bot.client.syncingController.pendingGroupMessageReceiptCacheList.contains { it.messageRandom == messageRandom }
|| msgHead.fromAppid == 3116 || msgHead.fromAppid == 2021) {
// 3116=group music share
// 2021=group file
// message sent by bot
collect(
SendGroupMessageReceipt(
messageRandom,
msgHead.msgSeq,
msgHead.fromAppid
)
)
return
}
// else: sync form other device
}
if (msgHead.groupInfo == null) return
val group = bot.getGroup(msgHead.groupInfo.groupCode) as GroupImpl? ?: return // 机器人还正在进群
// fragmented message
val msgs = group.groupPkgMsgParsingCache.tryMerge(data).ifEmpty { return }
var extraInfo: ImMsgBody.ExtraInfo? = null
var anonymous: ImMsgBody.AnonymousGroupMsg? = null
for (msg in msgs) {
for (elem in msg.msg.msgBody.richText.elems) {
when {
elem.extraInfo != null -> extraInfo = elem.extraInfo
elem.anonGroupMsg != null -> anonymous = elem.anonGroupMsg
}
}
}
val sender: Member // null if sync from other client
val nameCard: MemberNick
if (anonymous != null) { // anonymous member
sender = group.newAnonymous(anonymous.anonNick.encodeToString(), anonymous.anonId.encodeBase64())
nameCard = sender.generateMemberNickFromMember()
} else { // normal member chat
sender = group[msgHead.fromUin] ?: kotlin.run {
bot.network.logger.warning { "Failed to find member ${msgHead.fromUin} in group ${group.id}" }
return
}
nameCard = findSenderName(extraInfo, msgHead.groupInfo) ?: sender.generateMemberNickFromMember()
}
sender.info?.castOrNull<MemberInfoImpl>()?.run {
lastSpeakTimestamp = currentTimeSeconds().toInt()
}
if (isFromSelfAccount) {
collect(
GroupMessageSyncEvent(
message = msgs.map { it.msg }.toMessageChainOnline(bot, group.id, MessageSourceKind.GROUP),
time = msgHead.msgTime,
group = group,
sender = sender,
senderName = nameCard.nick,
)
)
return
} else {
broadcastNameCardChangedEventIfNecessary(sender, nameCard)
collect(
GroupMessageEvent(
senderName = nameCard.nick,
sender = sender,
message = msgs.map { it.msg }.toMessageChainOnline(bot, group.id, MessageSourceKind.GROUP),
permission = sender.permission,
time = msgHead.msgTime
)
)
return
}
}
private suspend inline fun broadcastNameCardChangedEventIfNecessary(
sender: Member,
new: MemberNick
) {
if (sender is NormalMemberImpl) {
val currentNameCard = sender.nameCard
if (new.isNameCard) {
new.nick.let { name ->
if (currentNameCard != name) {
sender._nameCard = name
MemberCardChangeEvent(currentNameCard, name, sender).broadcast()
}
}
} else {
// 说明删除了群名片
if (currentNameCard.isNotEmpty()) {
sender._nameCard = ""
MemberCardChangeEvent(currentNameCard, "", sender).broadcast()
}
}
}
}
private fun findSenderName(
extraInfo: ImMsgBody.ExtraInfo?,
groupInfo: MsgComm.GroupInfo
): MemberNick? =
extraInfo?.groupCard?.takeIf { it.isNotEmpty() }?.decodeCommCardNameBuf()?.let {
MemberNick(it, true)
} ?: groupInfo.takeIf { it.groupCard.isNotEmpty() }?.let {
MemberNick(it.groupCard, it.groupCardType != 2)
}
private fun ByteArray.decodeCommCardNameBuf() = kotlin.runCatching {
if (this[0] == 0x0A.toByte()) {
val nameBuf = loadAs(Oidb0x8fc.CommCardNameBuf.serializer())
if (nameBuf.richCardName.isNotEmpty()) {
return@runCatching nameBuf.richCardName.joinToString("") { it.text.encodeToString() }
}
}
return@runCatching null
}.getOrNull() ?: encodeToString()
}

View File

@ -0,0 +1,358 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.notice
import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte
import kotlinx.io.core.readUShort
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.internal.contact.*
import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl
import net.mamoe.mirai.internal.getGroupByUin
import net.mamoe.mirai.internal.message.OnlineMessageSourceFromFriendImpl
import net.mamoe.mirai.internal.message.toMessageChainOnline
import net.mamoe.mirai.internal.network.components.ContactUpdater
import net.mamoe.mirai.internal.network.components.MsgCommonMsgProcessor
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.protocol.data.proto.FrdSysMsg
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.data.proto.SubMsgType0x7
import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.buildMessageChain
import net.mamoe.mirai.utils.*
internal class SystemMessageProcessor : MsgCommonMsgProcessor(), GroupEventProcessorContext {
companion object {
val KEY_FROM_SYNC = TypeKey<Boolean>("fromSync")
val PipelineContext.fromSync get() = attributes[KEY_FROM_SYNC]
}
override suspend fun PipelineContext.process0(data: MsgComm.Msg): Unit = data.run {
// TODO: 2021/6/26 extract logic into multiple processors
when (msgHead.msgType) {
33 -> bot.components[ContactUpdater].groupListModifyLock.withLock {
}
34 -> { // 与 33 重复
return
}
38 -> bot.components[ContactUpdater].groupListModifyLock.withLock { // 建群
bot.createGroupForBot(msgHead.fromUin)
?.let { collect(BotJoinGroupEvent.Active(it)) }
return
}
85 -> bot.components[ContactUpdater].groupListModifyLock.withLock { // 其他客户端入群
// msgHead.authUin: 处理人
if (msgHead.toUin == bot.id) {
bot.createGroupForBot(msgHead.fromUin)
?.let { collect(BotJoinGroupEvent.Active(it)) }
}
return
}
/*
34 -> { // 主动入群
// 回答了问题, 还需要管理员审核
// msgContent=27 0B 60 E7 01 76 E4 B8 DD 82 00 30 45 41 31 30 35 35 42 44 39 39 42 35 37 46 44 31 41 31 46 36 42 43 42 43 33 43 42 39 34 34 38 31 33 34 42 36 31 46 38 45 43 39 38 38 43 39 37 33
// msgContent=27 0B 60 E7 01 76 E4 B8 DD 02 00 30 44 44 41 43 44 33 35 43 31 39 34 30 46 42 39 39 34 46 43 32 34 43 39 32 33 39 31 45 42 35 32 33 46 36 30 37 35 42 41 38 42 30 30 37 42 36 42 41
// 回答正确问题, 直接加入
// 27 0B 60 E7 01 76 E4 B8 DD 82 00 30 43 37 37 39 41 38 32 44 38 33 30 35 37 38 31 33 37 45 42 39 35 43 42 45 36 45 43 38 36 34 38 44 34 35 44 42 33 44 45 37 34 41 36 30 33 37 46 45
// 提交验证消息加入, 需要审核
// 被踢了??
// msgContent=27 0B 60 E7 01 76 E4 B8 DD 83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 46 46 32 33 36 39 35 33 31 37 42 44 46 37 43 36 39 34 37 41 45 38 39 43 45 43 42 46 33 41 37 35 39 34 39 45 36 37 33 37 31 41 39 44 33 33 45 33
/*
// 搜索后直接加入群
soutv 17:43:32 : 33类型的content = 27 0B 60 E7 01 07 6E 47 BA 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 32 30 39 39 42 39 41 46 32 39 41 35 42 33 46 34 32 30 44 36 44 36 39 35 44 38 45 34 35 30 46 30 45 30 38 45 31 41 39 42 46 46 45 32 30 32 34 35
soutv 17:43:32 : 主动入群content = 2A 3D F5 69 01 35 D7 10 EA 83 4C EF 4F DD 06 B9 DC C0 ED D4 B1 00 30 37 41 39 31 39 34 31 41 30 37 46 38 32 31 39 39 43 34 35 46 39 30 36 31 43 37 39 37 33 39 35 43 34 44 36 31 33 43 31 35 42 37 32 45 46 43 43 36
*/
val group = bot.getGroupByUinOrNull(msgHead.fromUin)
group ?: return
msgBody.msgContent.soutv("主动入群content")
if (msgBody.msgContent.read {
discardExact(4) // group code
discardExact(1) // 1
discardExact(4) // requester uin
readByte().toInt().and(0xff)
// 0x02: 回答正确问题直接加入
// 0x82: 回答了问题, 或者有验证消息, 需要管理员审核
// 0x83: 回答正确问题直接加入
} != 0x82) {
if (group.members.contains(msgHead.authUin)) {
return
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
return MemberJoinEvent.Active(group.newMember(getNewMemberInfo())
.also { group.members.delegate.addLast(it) })
} else return
}
*/
//167 单向好友
166, 167 -> {
//我也不知道为什么要这样写,但它就是能跑
if (msgHead.fromUin == bot.id && fromSync) {
loop@ while (true) {
val instance = bot.client.getFriendSeq()
if (instance < msgHead.msgSeq) {
if (bot.client.setFriendSeq(instance, msgHead.msgSeq)) {
break@loop
}
} else break@loop
}
return
}
if (!bot.components[SsoProcessor].firstLoginSucceed) {
return
}
val fromUin = if (fromSync) {
msgHead.toUin
} else {
msgHead.fromUin
}
bot.getFriend(fromUin)?.let { friend ->
friend.checkIsFriendImpl()
friend.lastMessageSequence.loop {
//我也不知道为什么要这样写,但它就是能跑
if (friend.lastMessageSequence.value != msgHead.msgSeq
&& friend.lastMessageSequence.compareAndSet(it, msgHead.msgSeq)
&& contentHead?.autoReply != 1
) {
val msgs = friend.friendPkgMsgParsingCache.tryMerge(this)
if (msgs.isNotEmpty()) {
collect(
if (fromSync) {
FriendMessageSyncEvent(
friend,
msgs.toMessageChainOnline(bot, 0, MessageSourceKind.FRIEND),
msgHead.msgTime
)
} else {
FriendMessageEvent(
friend,
msgs.toMessageChainOnline(bot, 0, MessageSourceKind.FRIEND),
msgHead.msgTime
)
}
)
} else return
}
return
}
} ?: bot.getStranger(fromUin)?.let { stranger ->
stranger.checkIsImpl()
stranger.lastMessageSequence.loop {
//我也不知道为什么要这样写,但它就是能跑
if (stranger.lastMessageSequence.value != msgHead.msgSeq && stranger.lastMessageSequence.compareAndSet(
it,
msgHead.msgSeq
) && contentHead?.autoReply != 1
) {
collect(
if (fromSync) {
StrangerMessageSyncEvent(
stranger,
listOf(this).toMessageChainOnline(bot, 0, MessageSourceKind.STRANGER),
msgHead.msgTime
)
} else {
StrangerMessageEvent(
stranger,
listOf(this).toMessageChainOnline(bot, 0, MessageSourceKind.STRANGER),
msgHead.msgTime
)
}
)
}
return
}
}
return
}
208 -> {
// friend ptt
val target = bot.getFriend(msgHead.fromUin) ?: return
val lsc = listOf(this).toMessageChainOnline(bot, 0, MessageSourceKind.FRIEND)
collect(FriendMessageEvent(target, lsc, msgHead.msgTime))
return
}
529 -> {
// top_package/awbk.java:3765
when (msgHead.c2cCmd) {
// other client sync
7 -> {
val body = msgBody.msgContent.loadAs(SubMsgType0x7.MsgBody.serializer())
val textMsg =
body.msgSubcmd0x4Generic?.buf?.loadAs(SubMsgType0x7.MsgBody.QQDataTextMsg.serializer())
?: return
with(body.msgHeader ?: return) {
if (dstUin != bot.id) return
val client = bot.otherClients.find { it.appId == srcInstId }
?: return// don't compare with dstAppId. diff.
val chain = buildMessageChain {
+OnlineMessageSourceFromFriendImpl(bot, listOf(data))
for (msgItem in textMsg.msgItems) {
when (msgItem.type) {
1 -> +PlainText(msgItem.text)
else -> {
}
}
}
}
collect(OtherClientMessageEvent(client, chain, msgHead.msgTime))
}
}
}
// 各种垃圾
// 08 04 12 1E 08 E9 07 10 B7 F7 8B 80 02 18 E9 07 20 00 28 DD F1 92 B7 07 30 DD F1 92 B7 07 48 02 50 03 32 1E 08 88 80 F8 92 CD 84 80 80 10 10 01 18 00 20 01 2A 0C 0A 0A 08 01 12 06 E5 95 8A E5 95 8A
}
141 -> {
if (!bot.components[SsoProcessor].firstLoginSucceed || msgHead.fromUin == bot.id && !fromSync) {
return
}
val tmpHead = msgHead.c2cTmpMsgHead ?: return
val member = bot.getGroupByUin(tmpHead.groupUin)?.get(
if (fromSync) {
msgHead.toUin
} else {
msgHead.fromUin
}
)
?: return
member.checkIsMemberImpl()
member.lastMessageSequence.loop { instant ->
if (member.lastMessageSequence.value != msgHead.msgSeq && contentHead?.autoReply != 1) {
if (member.lastMessageSequence.compareAndSet(instant, msgHead.msgSeq)) {
collect(
if (fromSync) {
GroupTempMessageSyncEvent(
member,
listOf(this).toMessageChainOnline(bot, 0, MessageSourceKind.TEMP),
msgHead.msgTime
)
} else {
GroupTempMessageEvent(
member,
listOf(this).toMessageChainOnline(bot, 0, MessageSourceKind.TEMP),
msgHead.msgTime
)
}
)
}
} else return
}
}
84, 87 -> { // 请求入群验证 和 被要求入群
bot.network.run {
NewContact.SystemMsgNewGroup(bot.client).sendWithoutExpect()
}
return
}
187 -> { // 请求加好友验证
bot.network.run {
NewContact.SystemMsgNewFriend(bot.client).sendWithoutExpect()
}
return
}
732 -> {
// unknown
// 前 4 byte 是群号
return
}
//陌生人添加信息
191 -> {
var fromGroup = 0L
var pbNick = ""
msgBody.msgContent.read {
readUByte()// version
discardExact(readUByte().toInt())//skip
readUShort()//source id
readUShort()//SourceSubID
discardExact(readUShort().toLong())//skip size
if (readUShort().toInt() != 0) {//hasExtraInfo
discardExact(readUShort().toInt())//mail address info, skip
}
discardExact(4 + readUShort().toInt())//skip
for (i in 1..readUByte().toInt()) {//pb size
val type = readUShort().toInt()
val pbArray = ByteArray(readUShort().toInt() and 0xFF)
readAvailable(pbArray)
when (type) {
1000 -> pbArray.loadAs(FrdSysMsg.GroupInfo.serializer()).let { fromGroup = it.groupUin }
1002 -> pbArray.loadAs(FrdSysMsg.FriendMiscInfo.serializer())
.let { pbNick = it.fromuinNick }
else -> {
}//ignore
}
}
}
val nick =
sequenceOf(msgHead.fromNick, msgHead.authNick, pbNick).filter { it.isNotEmpty() }.firstOrNull()
?: return
val id =
sequenceOf(msgHead.fromUin, msgHead.authUin).filter { it != 0L }.firstOrNull() ?: return//对方QQ
Mirai.newStranger(bot, StrangerInfoImpl(id, nick, fromGroup)).checkIsImpl().let {
bot.getStranger(id)?.let { previous ->
bot.strangers.remove(id)
StrangerRelationChangeEvent.Deleted(previous).broadcast()
}
bot.strangers.delegate.add(it)
collect(StrangerAddEvent(it))
}
}
// 732: 27 0B 60 E7 0C 01 3E 03 3F A2 5E 90 60 E2 00 01 44 71 47 90 00 00 02 58
// 732: 27 0B 60 E7 11 00 40 08 07 20 E7 C1 AD B8 02 5A 36 08 B4 E7 E0 F0 09 1A 1A 08 9C D4 16 10 F7 D2 D8 F5 05 18 D0 E2 85 F4 06 20 00 28 00 30 B4 E7 E0 F0 09 2A 0E 08 00 12 0A 08 9C D4 16 10 00 18 01 20 00 30 00 38 00
// 732: 27 0B 60 E7 11 00 33 08 07 20 E7 C1 AD B8 02 5A 29 08 EE 97 85 E9 01 1A 19 08 EE D6 16 10 FF F2 D8 F5 05 18 E9 E7 A3 05 20 00 28 00 30 EE 97 85 E9 01 2A 02 08 00 30 00 38 00
else -> {
bot.network.logger.debug { "unknown PbGetMsg type ${msgHead.msgType}, data=${msgBody.msgContent.toUHexString()}" }
return
}
}
}
// kotlin bug, don't remove
private inline fun kotlinx.atomicfu.AtomicInt.loop(action: (Int) -> Unit): Nothing {
while (true) {
action(value)
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.notice.decoders
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUInt
import net.mamoe.mirai.internal.contact.GroupImpl
import net.mamoe.mirai.internal.contact.checkIsGroupImpl
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgInfo
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.debug
import net.mamoe.mirai.utils.read
import net.mamoe.mirai.utils.toUHexString
/**
* Decodes [MsgInfo] and re-fire [MsgType0x210] or [MsgType0x2DC]
*/
internal class MsgInfoDecoder(
private val logger: MiraiLogger,
) : SimpleNoticeProcessor<MsgInfo>(type()) {
override suspend fun PipelineContext.process0(data: MsgInfo) {
when (data.shMsgType.toUShort().toInt()) {
// 528
0x210 -> fire(data.vMsg.loadAs(MsgType0x210.serializer()))
// 732
0x2dc -> {
data.vMsg.read {
val group = bot.getGroup(readUInt().toLong()) ?: return // group has not been initialized
group.checkIsGroupImpl()
val kind = readByte().toInt()
discardExact(1)
fire(MsgType0x2DC(kind, group, this.readBytes()))
}
}
else -> {
logger.debug { "Unknown kind ${data.shMsgType.toInt()}, data=${data.vMsg.toUHexString()}" }
}
}
}
}
internal class MsgType0x2DC(
val kind: Int, // inner kind, read from vMsg
val group: GroupImpl,
val buf: ByteArray
)

View File

@ -0,0 +1,25 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.notice.decoders
import net.mamoe.mirai.internal.network.components.PipelineContext
import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210
internal class MsgType0x210Decoder : SimpleNoticeProcessor<MsgType0x210>(type()) {
override suspend fun PipelineContext.process0(data: MsgType0x210) {
when (data.uSubMsgType) {
0x8AL -> {
}
else -> {
}
}
}
}

View File

@ -146,7 +146,7 @@ internal inline fun <R : Packet?> IncomingPacketFactory<R>.buildResponseUniPacke
key: ByteArray = client.wLoginSigInfo.d2Key,
extraData: ByteReadPacket = BRP_STUB,
sequenceId: Int = client.nextSsoSequenceId(),
body: BytePacketBuilder.(sequenceId: Int) -> Unit
body: BytePacketBuilder.(sequenceId: Int) -> Unit = {}
): OutgoingPacketWithRespType<R> {
@Suppress("DuplicatedCode")
return OutgoingPacketWithRespType(name, commandName, sequenceId, buildPacket {

View File

@ -13,20 +13,13 @@ package net.mamoe.mirai.internal.network.protocol.packet.chat
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
import net.mamoe.mirai.event.events.BotLeaveEvent
import net.mamoe.mirai.event.events.MemberJoinRequestEvent
import net.mamoe.mirai.event.events.NewFriendRequestEvent
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.network.MultiPacketByIterable
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.ParseErrorPacket
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.*
import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.noticeProcessorPipeline
import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf
import kotlin.math.max
@ -86,7 +79,7 @@ internal class NewContact {
when {
packets.isEmpty() -> null
packets.size == 1 -> packets[0]
else -> MultiPacketByIterable(packets)
else -> MultiPacket(packets)
}
}.also {
bot.client.syncingController.run {
@ -168,86 +161,6 @@ internal class NewContact {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Packet? {
fun handleStruct(struct: Structmsg.StructMsg): Packet? {
return struct.msg?.run {
when (subType) {
1 -> { // 处理被邀请入群 或 处理成员入群申请
when (groupMsgType) {
1 -> {
// 成员申请入群
MemberJoinRequestEvent(
bot, struct.msgSeq, msgAdditional,
struct.reqUin, groupCode, groupName, reqUinNick
)
}
2 -> {
// Bot 被邀请入群
BotInvitedJoinGroupRequestEvent(
bot, struct.msgSeq, actionUin,
groupCode, groupName, actionUinNick
)
}
22 -> {
// 成员邀请入群
MemberJoinRequestEvent(
bot, struct.msgSeq, msgAdditional,
struct.reqUin, groupCode, groupName, reqUinNick, actionUin
)
}
else -> throw contextualBugReportException(
"parse SystemMsgNewGroup, subType=1",
this._miraiContentToString(),
additional = "并尽量描述此时机器人是否正被邀请加入群, 或者是有有新群员加入此群"
)
}
}
2 -> { // 被邀请入群, 自动同意, 不需处理
// val group = bot.getNewGroup(groupCode) ?: return null
// val invitor = group[actionUin]
//
// BotJoinGroupEvent.Invite(invitor)
null
}
3 -> { // 已被请他管理员处理
null
}
5 -> {
val group = bot.getGroup(groupCode) ?: return null
when (groupMsgType) {
3 -> {
// https://github.com/mamoe/mirai/issues/651
// msgDescribe=将你设置为管理员
// msgTitle=管理员设置
null
}
13 -> { // 成员主动退出, 机器人是管理员, 接到通知
// 但无法获取是哪个成员.
null
}
7 -> { // 机器人被踢
val operator = group[actionUin] ?: return null
BotLeaveEvent.Kick(operator)
}
else -> {
throw contextualBugReportException(
"解析 NewContact.SystemMsgNewGroup, subType=5, groupMsgType=$groupMsgType",
this._miraiContentToString(),
null,
"并描述此时机器人是否被踢出群等"
)
}
}
}
else -> throw contextualBugReportException(
"解析 NewContact.SystemMsgNewGroup, subType=$subType, groupMsgType=$groupMsgType",
forDebug = this._miraiContentToString(),
additional = "并尽量描述此时机器人是否正被邀请加入群, 或者是有有新群员加入此群"
)
}
}
}
return readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run {
groupmsgs.filter {
it.msgTime >= bot.client.syncingController.latestMsgNewGroupTime
@ -262,17 +175,11 @@ internal class NewContact {
return@mapNotNull null
}
try {
handleStruct(struct)
bot.components.noticeProcessorPipeline.process(bot, struct).toPacket()
} catch (e: Throwable) {
ParseErrorPacket(e)
}
}.let { packets ->
when {
packets.isEmpty() -> null
packets.size == 1 -> packets[0]
else -> MultiPacketByIterable(packets)
}
}.also {
}.toPacket().also {
bot.client.syncingController.run {
latestMsgNewGroupTime = max(latestMsgNewGroupTime, groupmsgs.maxOfOrNull { it.msgTime } ?: 0)
}

View File

@ -16,47 +16,22 @@ import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.*
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.Bot
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.NormalMember
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.*
import net.mamoe.mirai.internal.contact.info.GroupInfoImpl
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl
import net.mamoe.mirai.internal.message.OnlineMessageSourceFromFriendImpl
import net.mamoe.mirai.internal.message.toMessageChainOnline
import net.mamoe.mirai.internal.network.MultiPacket
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.components.ContactUpdater
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.protocol.data.proto.FrdSysMsg
import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.noticeProcessorPipeline
import net.mamoe.mirai.internal.network.notice.SystemMessageProcessor
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.internal.network.protocol.data.proto.SubMsgType0x7
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.MessageSourceKind.*
import net.mamoe.mirai.utils.cast
import net.mamoe.mirai.utils.debug
import net.mamoe.mirai.utils.read
import net.mamoe.mirai.utils.toUHexString
import kotlin.random.Random
@ -107,10 +82,9 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
internal val syncFlagFromServer: MsgSvc.SyncFlag,
delegate: List<Packet>,
val syncCookie: ByteArray?, override val bot: Bot
) :
AbstractEvent(),
MultiPacket<Packet>,
Iterable<Packet> by (delegate),
) : AbstractEvent(),
MultiPacket,
Collection<Packet> by delegate,
Packet.NoEventLog,
BotEvent {
@ -183,8 +157,9 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
)
}
.flatMapConcat { msg ->
val result = msg.transform(bot)
if (result == null) emptyFlow() else flowOf(result)
bot.components.noticeProcessorPipeline
.process(bot, msg, SystemMessageProcessor.KEY_FROM_SYNC to false)
.asFlow()
}
val list: List<Packet> = messages.toList()
@ -224,398 +199,3 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
}
}
}
internal suspend fun QQAndroidBot.createGroupForBot(groupUin: Long): Group? {
val group = getGroupByUinOrNull(groupUin)
if (group != null) {
return null
}
return getNewGroup(Mirai.calculateGroupCodeByGroupUin(groupUin))?.apply { groups.delegate.add(this) }
}
private fun MsgComm.Msg.getNewMemberInfo(): MemberInfo {
return MemberInfoImpl(
nameCard = msgHead.authNick.ifEmpty { msgHead.fromNick },
permission = MemberPermission.MEMBER,
specialTitle = "",
muteTimestamp = 0,
uin = msgHead.authUin,
nick = msgHead.authNick.ifEmpty { msgHead.fromNick },
remark = "",
anonymousId = null
)
}
internal suspend fun MsgComm.Msg.transform(bot: QQAndroidBot, fromSync: Boolean = false): Packet? {
when (msgHead.msgType) {
33 -> bot.components[ContactUpdater].groupListModifyLock.withLock {
msgBody.msgContent.read {
val groupUin = Mirai.calculateGroupUinByGroupCode(readUInt().toLong())
val group = bot.getGroupByUinOrNull(groupUin) ?: bot.createGroupForBot(groupUin) ?: return null
discardExact(1)
val joinedMemberUin = readUInt().toLong()
val joinType = readByte().toInt()
val invitorUin = readUInt().toLong()
return when (joinType) {
//邀请加入
-125, 3 -> {
val invitor = if (invitorUin == bot.id) {
group.botAsMember
} else {
group[invitorUin]
} ?: return null
if (joinedMemberUin == bot.id) {
BotJoinGroupEvent.Invite(invitor)
} else {
MemberJoinEvent.Invite(
group.newMember(getNewMemberInfo()).cast<NormalMember>()
.also { group.members.delegate.add(it) }, invitor
)
}
}
//通过群员分享的二维码/直接加入
-126, 2 -> {
if (joinedMemberUin == bot.id) {
BotJoinGroupEvent.Active(group)
} else {
MemberJoinEvent.Active(
group.newMember(getNewMemberInfo()).cast<NormalMember>()
.also { group.members.delegate.add(it) })
}
}
//忽略
else -> {
null
}
}
}
// 邀请入群
// package: 27 0B 60 E7 01 CA CC 69 8B 83 44 71 47 90 06 B9 DC C0 ED D4 B1 00 30 33 44 30 42 38 46 30 39 37 32 38 35 43 34 31 38 30 33 36 41 34 36 31 36 31 35 32 37 38 46 46 43 30 41 38 30 36 30 36 45 38 31 43 39 41 34 38 37
// package: groupUin + 01 CA CC 69 8B 83 + invitorUin + length(06) + string + magicKey
// 主动入群, 直接加入: msgContent=27 0B 60 E7 01 76 E4 B8 DD 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 42 39 41 30 33 45 38 34 30 39 34 42 46 30 45 32 45 38 42 31 43 43 41 34 32 42 38 42 44 42 35 34 44 42 31 44 32 32 30 46 30 38 39 46 46 35 41 38
// 主动直接加入 27 0B 60 E7 01 76 E4 B8 DD 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 33 30 45 38 42 31 33 46 41 41 31 33 46 38 31 35 34 41 38 33 32 37 31 43 34 34 38 35 33 35 46 45 31 38 32 43 39 42 43 46 46 32 44 39 39 46 41 37
// 有人被邀请(经过同意后)加入 27 0B 60 E7 01 76 E4 B8 DD 83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 34 30 34 38 32 33 38 35 37 41 37 38 46 33 45 37 35 38 42 39 38 46 43 45 44 43 32 41 30 31 36 36 30 34 31 36 39 35 39 30 38 39 30 39 45 31 34 34
// 搜索到群, 直接加入 27 0B 60 E7 01 07 6E 47 BA 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 32 30 39 39 42 39 41 46 32 39 41 35 42 33 46 34 32 30 44 36 44 36 39 35 44 38 45 34 35 30 46 30 45 30 38 45 31 41 39 42 46 46 45 32 30 32 34 35
}
34 -> { // 与 33 重复
return null
}
38 -> bot.components[ContactUpdater].groupListModifyLock.withLock { // 建群
return bot.createGroupForBot(msgHead.fromUin)
?.let { BotJoinGroupEvent.Active(it) }
}
85 -> bot.components[ContactUpdater].groupListModifyLock.withLock { // 其他客户端入群
// msgHead.authUin: 处理人
return if (msgHead.toUin == bot.id) {
bot.createGroupForBot(msgHead.fromUin)
?.let { BotJoinGroupEvent.Active(it) }
} else {
null
}
}
/*
34 -> { // 主动入群
// 回答了问题, 还需要管理员审核
// msgContent=27 0B 60 E7 01 76 E4 B8 DD 82 00 30 45 41 31 30 35 35 42 44 39 39 42 35 37 46 44 31 41 31 46 36 42 43 42 43 33 43 42 39 34 34 38 31 33 34 42 36 31 46 38 45 43 39 38 38 43 39 37 33
// msgContent=27 0B 60 E7 01 76 E4 B8 DD 02 00 30 44 44 41 43 44 33 35 43 31 39 34 30 46 42 39 39 34 46 43 32 34 43 39 32 33 39 31 45 42 35 32 33 46 36 30 37 35 42 41 38 42 30 30 37 42 36 42 41
// 回答正确问题, 直接加入
// 27 0B 60 E7 01 76 E4 B8 DD 82 00 30 43 37 37 39 41 38 32 44 38 33 30 35 37 38 31 33 37 45 42 39 35 43 42 45 36 45 43 38 36 34 38 44 34 35 44 42 33 44 45 37 34 41 36 30 33 37 46 45
// 提交验证消息加入, 需要审核
// 被踢了??
// msgContent=27 0B 60 E7 01 76 E4 B8 DD 83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 46 46 32 33 36 39 35 33 31 37 42 44 46 37 43 36 39 34 37 41 45 38 39 43 45 43 42 46 33 41 37 35 39 34 39 45 36 37 33 37 31 41 39 44 33 33 45 33
/*
// 搜索后直接加入群
soutv 17:43:32 : 33类型的content = 27 0B 60 E7 01 07 6E 47 BA 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 32 30 39 39 42 39 41 46 32 39 41 35 42 33 46 34 32 30 44 36 44 36 39 35 44 38 45 34 35 30 46 30 45 30 38 45 31 41 39 42 46 46 45 32 30 32 34 35
soutv 17:43:32 : 主动入群content = 2A 3D F5 69 01 35 D7 10 EA 83 4C EF 4F DD 06 B9 DC C0 ED D4 B1 00 30 37 41 39 31 39 34 31 41 30 37 46 38 32 31 39 39 43 34 35 46 39 30 36 31 43 37 39 37 33 39 35 43 34 44 36 31 33 43 31 35 42 37 32 45 46 43 43 36
*/
val group = bot.getGroupByUinOrNull(msgHead.fromUin)
group ?: return null
msgBody.msgContent.soutv("主动入群content")
if (msgBody.msgContent.read {
discardExact(4) // group code
discardExact(1) // 1
discardExact(4) // requester uin
readByte().toInt().and(0xff)
// 0x02: 回答正确问题直接加入
// 0x82: 回答了问题, 或者有验证消息, 需要管理员审核
// 0x83: 回答正确问题直接加入
} != 0x82) {
if (group.members.contains(msgHead.authUin)) {
return null
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
return MemberJoinEvent.Active(group.newMember(getNewMemberInfo())
.also { group.members.delegate.addLast(it) })
} else return null
}
*/
//167 单向好友
166, 167 -> {
//我也不知道为什么要这样写,但它就是能跑
if (msgHead.fromUin == bot.id && !fromSync) {
loop@ while (true) {
val instance = bot.client.getFriendSeq()
if (instance < msgHead.msgSeq) {
if (bot.client.setFriendSeq(instance, msgHead.msgSeq)) {
break@loop
}
} else break@loop
}
return null
}
if (!bot.components[SsoProcessor].firstLoginSucceed) {
return null
}
val fromUin = if (fromSync) {
msgHead.toUin
} else {
msgHead.fromUin
}
bot.getFriend(fromUin)?.let { friend ->
friend.checkIsFriendImpl()
friend.lastMessageSequence.loop {
//我也不知道为什么要这样写,但它就是能跑
return if (friend.lastMessageSequence.value != msgHead.msgSeq
&& friend.lastMessageSequence.compareAndSet(it, msgHead.msgSeq)
&& contentHead?.autoReply != 1
) {
val msgs = friend.friendPkgMsgParsingCache.tryMerge(this)
if (msgs.isNotEmpty()) {
if (fromSync) {
FriendMessageSyncEvent(
friend,
msgs.toMessageChainOnline(bot, 0, MessageSourceKind.FRIEND),
msgHead.msgTime
)
} else {
FriendMessageEvent(
friend,
msgs.toMessageChainOnline(bot, 0, MessageSourceKind.FRIEND),
msgHead.msgTime
)
}
} else return null
} else null
}
} ?: bot.getStranger(fromUin)?.let { stranger ->
stranger.checkIsImpl()
stranger.lastMessageSequence.loop {
//我也不知道为什么要这样写,但它就是能跑
return if (stranger.lastMessageSequence.value != msgHead.msgSeq && stranger.lastMessageSequence.compareAndSet(
it,
msgHead.msgSeq
) && contentHead?.autoReply != 1
) {
if (fromSync) {
StrangerMessageSyncEvent(
stranger,
listOf(this).toMessageChainOnline(bot, 0, STRANGER),
msgHead.msgTime
)
} else {
StrangerMessageEvent(
stranger,
listOf(this).toMessageChainOnline(bot, 0, STRANGER),
msgHead.msgTime
)
}
} else null
}
} ?: return null
}
208 -> {
// friend ptt
val target = bot.getFriend(msgHead.fromUin)
?: return null
val lsc = listOf(this).toMessageChainOnline(bot, 0, FRIEND)
return FriendMessageEvent(target, lsc, msgHead.msgTime)
}
529 -> {
// top_package/awbk.java:3765
return when (msgHead.c2cCmd) {
// other client sync
7 -> {
val data = msgBody.msgContent.loadAs(SubMsgType0x7.MsgBody.serializer())
val textMsg =
data.msgSubcmd0x4Generic?.buf?.loadAs(SubMsgType0x7.MsgBody.QQDataTextMsg.serializer())
?: return null
with(data.msgHeader ?: return null) {
if (dstUin != bot.id) return null
val client = bot.otherClients.find { it.appId == srcInstId }
?: return null// don't compare with dstAppId. diff.
val chain = buildMessageChain {
+OnlineMessageSourceFromFriendImpl(bot, listOf(this@transform))
for (msgItem in textMsg.msgItems) {
when (msgItem.type) {
1 -> +PlainText(msgItem.text)
else -> {
}
}
}
}
return OtherClientMessageEvent(client, chain, msgHead.msgTime)
}
}
else -> null
}
// 各种垃圾
// 08 04 12 1E 08 E9 07 10 B7 F7 8B 80 02 18 E9 07 20 00 28 DD F1 92 B7 07 30 DD F1 92 B7 07 48 02 50 03 32 1E 08 88 80 F8 92 CD 84 80 80 10 10 01 18 00 20 01 2A 0C 0A 0A 08 01 12 06 E5 95 8A E5 95 8A
}
141 -> {
if (!bot.components[SsoProcessor].firstLoginSucceed || msgHead.fromUin == bot.id && !fromSync) {
return null
}
val tmpHead = msgHead.c2cTmpMsgHead ?: return null
val member = bot.getGroupByUinOrNull(tmpHead.groupUin)?.get(
if (fromSync) {
msgHead.toUin
} else {
msgHead.fromUin
}
)
?: return null
member.checkIsMemberImpl()
member.lastMessageSequence.loop { instant ->
if (member.lastMessageSequence.value != msgHead.msgSeq && contentHead?.autoReply != 1) {
if (member.lastMessageSequence.compareAndSet(instant, msgHead.msgSeq)) {
return if (fromSync) {
GroupTempMessageSyncEvent(
member,
listOf(this).toMessageChainOnline(bot, 0, TEMP),
msgHead.msgTime
)
} else {
GroupTempMessageEvent(
member,
listOf(this).toMessageChainOnline(bot, 0, TEMP),
msgHead.msgTime
)
}
}
} else return null
}
}
84, 87 -> { // 请求入群验证 和 被要求入群
bot.network.run {
NewContact.SystemMsgNewGroup(bot.client).sendWithoutExpect()
}
return null
}
187 -> { // 请求加好友验证
bot.network.run {
NewContact.SystemMsgNewFriend(bot.client).sendWithoutExpect()
}
return null
}
732 -> {
// unknown
// 前 4 byte 是群号
return null
}
//陌生人添加信息
191 -> {
var fromGroup = 0L
var pbNick = ""
msgBody.msgContent.read {
readUByte()// version
discardExact(readUByte().toInt())//skip
readUShort()//source id
readUShort()//SourceSubID
discardExact(readUShort().toLong())//skip size
if (readUShort().toInt() != 0) {//hasExtraInfo
discardExact(readUShort().toInt())//mail address info, skip
}
discardExact(4 + readUShort().toInt())//skip
for (i in 1..readUByte().toInt()) {//pb size
val type = readUShort().toInt()
val pbArray = ByteArray(readUShort().toInt() and 0xFF)
readAvailable(pbArray)
when (type) {
1000 -> pbArray.loadAs(FrdSysMsg.GroupInfo.serializer()).let { fromGroup = it.groupUin }
1002 -> pbArray.loadAs(FrdSysMsg.FriendMiscInfo.serializer()).let { pbNick = it.fromuinNick }
else -> {
}//ignore
}
}
}
val nick = sequenceOf(msgHead.fromNick, msgHead.authNick, pbNick).filter { it.isNotEmpty() }.firstOrNull()
?: return null
val id = sequenceOf(msgHead.fromUin, msgHead.authUin).filter { it != 0L }.firstOrNull() ?: return null//对方QQ
Mirai.newStranger(bot, StrangerInfoImpl(id, nick, fromGroup)).let {
bot.getStranger(id)?.let { previous ->
bot.strangers.remove(id)
StrangerRelationChangeEvent.Deleted(previous).broadcast()
}
bot.strangers.delegate.add(it)
return StrangerAddEvent(it)
}
}
// 732: 27 0B 60 E7 0C 01 3E 03 3F A2 5E 90 60 E2 00 01 44 71 47 90 00 00 02 58
// 732: 27 0B 60 E7 11 00 40 08 07 20 E7 C1 AD B8 02 5A 36 08 B4 E7 E0 F0 09 1A 1A 08 9C D4 16 10 F7 D2 D8 F5 05 18 D0 E2 85 F4 06 20 00 28 00 30 B4 E7 E0 F0 09 2A 0E 08 00 12 0A 08 9C D4 16 10 00 18 01 20 00 30 00 38 00
// 732: 27 0B 60 E7 11 00 33 08 07 20 E7 C1 AD B8 02 5A 29 08 EE 97 85 E9 01 1A 19 08 EE D6 16 10 FF F2 D8 F5 05 18 E9 E7 A3 05 20 00 28 00 30 EE 97 85 E9 01 2A 02 08 00 30 00 38 00
else -> {
bot.network.logger.debug { "unknown PbGetMsg type ${msgHead.msgType}, data=${msgBody.msgContent.toUHexString()}" }
return null
}
}
}
// kotlin bug, don't remove
private inline fun kotlinx.atomicfu.AtomicInt.loop(action: (Int) -> Unit): Nothing {
while (true) {
action(value)
}
}
internal suspend fun QQAndroidBot.getNewGroup(groupCode: Long): Group? {
val troopNum = network.run {
FriendList.GetTroopListSimplify(client)
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(timeoutMillis = 10_000, retry = 5)
}.groups.firstOrNull { it.groupCode == groupCode } ?: return null
return GroupImpl(
bot = this,
coroutineContext = coroutineContext,
id = groupCode,
groupInfo = GroupInfoImpl(troopNum),
members = Mirai.getRawGroupMemberList(
this,
troopNum.groupUin,
troopNum.groupCode,
troopNum.dwGroupOwnerUin
)
)
}

View File

@ -12,14 +12,21 @@ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.noticeProcessorPipeline
import net.mamoe.mirai.internal.network.notice.SystemMessageProcessor
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush
import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.internal.network.toPacket
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
internal object PbC2CMsgSync : IncomingPacketFactory<Packet?>(
internal object PbC2CMsgSync : IncomingPacketFactory<Packet>(
"OnlinePush.PbC2CMsgSync", ""
) {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
return readProtoBuf(MsgOnlinePush.PbPushMsg.serializer()).msg.transform(bot, true)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet {
return bot.components.noticeProcessorPipeline.process(
bot = bot,
data = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer()).msg,
attributes = SystemMessageProcessor.KEY_FROM_SYNC to true
).toPacket()
}
}

View File

@ -12,185 +12,25 @@
package net.mamoe.mirai.internal.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.GroupMessageEvent
import net.mamoe.mirai.event.events.GroupMessageSyncEvent
import net.mamoe.mirai.event.events.MemberCardChangeEvent
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.GroupImpl
import net.mamoe.mirai.internal.contact.NormalMemberImpl
import net.mamoe.mirai.internal.contact.info
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.contact.newAnonymous
import net.mamoe.mirai.internal.message.toMessageChainOnline
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.noticeProcessorPipeline
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush
import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x8fc
import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushPbPushGroupMsg.MemberNick.Companion.generateMemberNickFromMember
import net.mamoe.mirai.internal.network.toPacket
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.message.data.MessageSourceKind.GROUP
import net.mamoe.mirai.utils.*
/**
* 接受群消息
*/
internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory<Packet?>("OnlinePush.PbPushGroupMsg") {
internal class SendGroupMessageReceipt(
val messageRandom: Int,
val sequenceId: Int,
val fromAppId: Int,
) : Packet, Event, Packet.NoLog, AbstractEvent() {
override fun toString(): String {
return "OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt(messageRandom=$messageRandom, sequenceId=$sequenceId)"
}
companion object {
val EMPTY = SendGroupMessageReceipt(0, 0, 0)
}
}
internal data class MemberNick(val nick: String, val isNameCard: Boolean = false) {
companion object {
fun Member.generateMemberNickFromMember(): MemberNick {
return nameCard.takeIf { nameCard.isNotEmpty() }?.let {
MemberNick(nameCard, true)
} ?: MemberNick(nick, false)
}
}
}
@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.components[SsoProcessor].firstLoginSucceed) return null
val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer())
val msgHead = pbPushMsg.msg.msgHead
val isFromSelfAccount = msgHead.fromUin == bot.id
if (isFromSelfAccount) {
val messageRandom = pbPushMsg.msg.msgBody.richText.attr?.random ?: return null
if (bot.client.syncingController.pendingGroupMessageReceiptCacheList.contains { it.messageRandom == messageRandom }
|| msgHead.fromAppid == 3116 || msgHead.fromAppid == 2021) {
// 3116=group music share
// 2021=group file
// message sent by bot
return SendGroupMessageReceipt(
messageRandom,
msgHead.msgSeq,
msgHead.fromAppid
)
}
// else: sync form other device
}
if (msgHead.groupInfo == null) return null
val group = bot.getGroup(msgHead.groupInfo.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
// fragmented message
val msgs = group.groupPkgMsgParsingCache.tryMerge(pbPushMsg).ifEmpty { return null }
var extraInfo: ImMsgBody.ExtraInfo? = null
var anonymous: ImMsgBody.AnonymousGroupMsg? = null
for (msg in msgs) {
for (elem in msg.msg.msgBody.richText.elems) {
when {
elem.extraInfo != null -> extraInfo = elem.extraInfo
elem.anonGroupMsg != null -> anonymous = elem.anonGroupMsg
}
}
}
val sender: Member // null if sync from other client
val nameCard: MemberNick
if (anonymous != null) { // anonymous member
sender = group.newAnonymous(anonymous.anonNick.encodeToString(), anonymous.anonId.encodeBase64())
nameCard = sender.generateMemberNickFromMember()
} else { // normal member chat
sender = group[msgHead.fromUin] as NormalMemberImpl? ?: kotlin.run {
bot.network.logger.warning { "Failed to find member ${msgHead.fromUin} in group ${group.id}" }
return null
}
nameCard = findSenderName(extraInfo, msgHead.groupInfo) ?: sender.generateMemberNickFromMember()
}
sender.info?.castOrNull<MemberInfoImpl>()?.run {
lastSpeakTimestamp = currentTimeSeconds().toInt()
}
if (isFromSelfAccount) {
return GroupMessageSyncEvent(
message = msgs.map { it.msg }.toMessageChainOnline(bot, group.id, GROUP),
time = msgHead.msgTime,
group = group,
sender = sender,
senderName = nameCard.nick,
)
} else {
broadcastNameCardChangedEventIfNecessary(sender, nameCard)
return GroupMessageEvent(
senderName = nameCard.nick,
sender = sender,
message = msgs.map { it.msg }.toMessageChainOnline(bot, group.id, GROUP),
permission = sender.permission,
time = msgHead.msgTime
)
}
return bot.components.noticeProcessorPipeline.process(bot, readProtoBuf(MsgOnlinePush.PbPushMsg.serializer()))
.toPacket()
}
private suspend inline fun broadcastNameCardChangedEventIfNecessary(sender: Member, new: MemberNick) {
if (sender is NormalMemberImpl) {
val currentNameCard = sender.nameCard
if (new.isNameCard) {
new.nick.let { name ->
if (currentNameCard != name) {
sender._nameCard = name
MemberCardChangeEvent(currentNameCard, name, sender).broadcast()
}
}
} else {
// 说明删除了群名片
if (currentNameCard.isNotEmpty()) {
sender._nameCard = ""
MemberCardChangeEvent(currentNameCard, "", sender).broadcast()
}
}
}
}
private fun findSenderName(
extraInfo: ImMsgBody.ExtraInfo?,
groupInfo: MsgComm.GroupInfo
): MemberNick? = extraInfo?.groupCard?.takeIf { it.isNotEmpty() }?.decodeCommCardNameBuf()?.let {
MemberNick(it, true)
} ?: groupInfo.takeIf { it.groupCard.isNotEmpty() }?.let {
MemberNick(it.groupCard, it.groupCardType != 2)
}
private fun ByteArray.decodeCommCardNameBuf() = kotlin.runCatching {
if (this[0] == 0x0A.toByte()) {
val nameBuf = loadAs(Oidb0x8fc.CommCardNameBuf.serializer())
if (nameBuf.richCardName.isNotEmpty()) {
return@runCatching nameBuf.richCardName.joinToString("") { it.text.encodeToString() }
}
}
return@runCatching null
}.getOrNull() ?: encodeToString()
}

View File

@ -9,33 +9,16 @@
package net.mamoe.mirai.internal.network.protocol.packet.chat.receive
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.cancel
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.NormalMember
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.GroupImpl
import net.mamoe.mirai.internal.contact.NormalMemberImpl
import net.mamoe.mirai.internal.contact.checkIsMemberImpl
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.contact.newMember
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.network.MultiPacketByIterable
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.noticeProcessorPipeline
import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans
import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.utils.cast
import net.mamoe.mirai.utils.read
internal object OnlinePushPbPushTransMsg :
@ -56,263 +39,12 @@ internal object OnlinePushPbPushTransMsg :
// bot.network.logger.debug { content._miraiContentToString() }
content.msgData.read<Unit> {
when (content.msgType) {
44 -> {
// 3D C4 33 DD 01 FF CD 76 F4 03 C3 7E 2E 34
// 群转让
// start with 3D C4 33 DD 01 FF
// 3D C4 33 DD 01 FF C3 7E 2E 34 CD 76 F4 03
// 权限变更
// 3D C4 33 DD 01 00/01 .....
// 3D C4 33 DD 01 01 C3 7E 2E 34 01
this.discardExact(5)
when (val mode = readUByte().toInt()) {
0xFF -> {
// 群转让 / huifu.qq.com
// From -> to
val from = readUInt().toLong()
val to = readUInt().toLong()
val results = ArrayList<Packet>()
// println("$from -> $to")
if (to == bot.id) {
if (bot.getGroupByUinOrNull(content.fromUin) == null) {
MessageSvcPbGetMsg.run {
results.add(
BotJoinGroupEvent.Retrieve(
bot.createGroupForBot(content.fromUin)!!
)
)
}
}
}
val group = bot.getGroupByUin(content.fromUin) as GroupImpl
if (from == bot.id) {
if (group.botPermission != MemberPermission.MEMBER)
results.add(
BotGroupPermissionChangeEvent(
group, group.botPermission.also {
group.botAsMember.checkIsMemberImpl().permission =
MemberPermission.MEMBER
},
MemberPermission.MEMBER
)
)
} else {
val member = group[from] as NormalMemberImpl
if (member.permission != MemberPermission.MEMBER) {
results.add(
MemberPermissionChangeEvent(
member,
member.permission.also { member.permission = MemberPermission.MEMBER },
MemberPermission.MEMBER
)
)
}
}
if (to == bot.id) {
if (group.botPermission != MemberPermission.OWNER) {
results.add(
BotGroupPermissionChangeEvent(
group,
group.botAsMember.permission.also {
group.botAsMember.checkIsMemberImpl().permission =
MemberPermission.OWNER
},
MemberPermission.OWNER
)
)
}
} else {
val newOwner = (group[to] ?: group.newMember(
MemberInfoImpl(
to,
"",
MemberPermission.OWNER,
"",
"",
"",
0,
null
)
)).also { owner ->
owner.checkIsMemberImpl().permission = MemberPermission.OWNER
group.members.delegate.add(owner)
results.add(MemberJoinEvent.Retrieve(owner))
}.cast<NormalMember>()
if (newOwner.permission != MemberPermission.OWNER) {
results.add(
MemberPermissionChangeEvent(
newOwner,
newOwner.permission.also {
newOwner.checkIsMemberImpl().permission = MemberPermission.OWNER
},
MemberPermission.OWNER
)
)
}
}
return MultiPacketByIterable(results)
}
else -> {
var var5 = 0L
val target = readUInt().toLong()
if (mode != 0 && mode != 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 NormalMemberImpl
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() // groupCode
readByte().toInt() // follow type
val target = readUInt().toLong()
val type = readUByte().toInt()
val operator = readUInt().toLong()
val groupUin = content.fromUin
when (type) {
2, 0x82 -> bot.getGroupByUinOrNull(groupUin)?.let { group ->
if (target == bot.id) {
return BotLeaveEvent.Active(group).also {
group.cancel(CancellationException("Leaved actively"))
bot.groups.delegate.remove(group)
}
} else {
val member = group.get(target) as? NormalMemberImpl ?: return null
return MemberLeaveEvent.Quit(member.also {
member.cancel(CancellationException("Leaved actively"))
group.members.delegate.remove(member)
})
}
}
3, 0x83 -> bot.getGroupByUin(groupUin).let { group ->
if (target == bot.id) {
val member = group.members[operator] ?: return@let null
return BotLeaveEvent.Kick(member).also {
group.cancel(CancellationException("Being kicked"))
bot.groups.delegate.remove(group)
}
} else {
val member = group.get(target) as? NormalMemberImpl ?: return null
return MemberLeaveEvent.Kick(member.also {
member.cancel(CancellationException("Being kicked"))
group.members.delegate.remove(member)
}, group.members[operator])
}
}
}
}
else -> {
when {
content.msgType == 529 && content.msgSubtype == 9 -> {
/*
PbMsgInfo#1773430973 {
fromUin=0x0000000026BA1173(649728371)
generalFlag=0x00000001(1)
msgData=0A 07 70 72 69 6E 74 65 72 10 02 1A CD 02 0A 1F 53 61 6D 73 75 6E 67 20 4D 4C 2D 31 38 36 30 20 53 65 72 69 65 73 20 28 55 53 42 30 30 31 29 0A 16 4F 6E 65 4E 6F 74 65 20 66 6F 72 20 57 69 6E 64 6F 77 73 20 31 30 0A 19 50 68 61 6E 74 6F 6D 20 50 72 69 6E 74 20 74 6F 20 45 76 65 72 6E 6F 74 65 0A 11 4F 6E 65 4E 6F 74 65 20 28 44 65 73 6B 74 6F 70 29 0A 1D 4D 69 63 72 6F 73 6F 66 74 20 58 50 53 20 44 6F 63 75 6D 65 6E 74 20 57 72 69 74 65 72 0A 16 4D 69 63 72 6F 73 6F 66 74 20 50 72 69 6E 74 20 74 6F 20 50 44 46 0A 15 46 6F 78 69 74 20 50 68 61 6E 74 6F 6D 20 50 72 69 6E 74 65 72 0A 03 46 61 78 32 09 0A 03 6A 70 67 10 01 18 00 32 0A 0A 04 6A 70 65 67 10 01 18 00 32 09 0A 03 70 6E 67 10 01 18 00 32 09 0A 03 67 69 66 10 01 18 00 32 09 0A 03 62 6D 70 10 01 18 00 32 09 0A 03 64 6F 63 10 01 18 01 32 0A 0A 04 64 6F 63 78 10 01 18 01 32 09 0A 03 74 78 74 10 00 18 00 32 09 0A 03 70 64 66 10 01 18 01 32 09 0A 03 70 70 74 10 01 18 01 32 0A 0A 04 70 70 74 78 10 01 18 01 32 09 0A 03 78 6C 73 10 01 18 01 32 0A 0A 04 78 6C 73 78 10 01 18 01
msgSeq=0x00001AFF(6911)
msgSubtype=0x00000009(9)
msgTime=0x5FDF21A3(1608458659)
msgType=0x00000211(529)
msgUid=0x010000005FDEE04C(72057595646369868)
realMsgTime=0x5FDF21A3(1608458659)
svrIp=0x3E689409(1047041033)
toUin=0x0000000026BA1173(649728371)
}
*/
/*
*
printer
Samsung ML-1860 Series (USB001)
OneNote for Windows 10
Phantom Print to Evernote
OneNote (Desktop)
Microsoft XPS Document Writer
Microsoft Print to PDF
Foxit Phantom Printer
Fax2
jpg2
jpeg2
png2
gif2
bmp2
doc2
docx2
txt2
pdf2
ppt2
pptx2
xls2
xlsx*/
return null
}
}
throw contextualBugReportException(
"解析 OnlinePush.PbPushTransMsg, msgType=${content.msgType}",
content._miraiContentToString(),
null,
"并描述此时机器人是否被踢出, 或是否有成员列表变更等动作."
)
}
}
}
bot.components.noticeProcessorPipeline.process(bot, content)
return null
}
override suspend fun QQAndroidBot.handle(packet: Packet?, sequenceId: Int): OutgoingPacket? {
return buildResponseUniPacket(client, sequenceId = sequenceId) {}
override suspend fun QQAndroidBot.handle(packet: Packet?, sequenceId: Int): OutgoingPacket {
return buildResponseUniPacket(client, sequenceId = sequenceId)
}
}

View File

@ -14,22 +14,21 @@
package net.mamoe.mirai.internal.network.protocol.packet.chat.receive
import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.NormalMember
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.data.GroupHonorType
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.*
import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.network.MultiPacketBySequence
import net.mamoe.mirai.internal.network.MultiPacketImpl
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.components.ContactUpdater
import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgInfo
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210
@ -46,7 +45,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
import net.mamoe.mirai.internal.utils.*
import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.internal.utils.io.ProtoBuf
import net.mamoe.mirai.internal.utils.io.serialization.*
import net.mamoe.mirai.utils.*
@ -61,7 +60,7 @@ internal object OnlinePushReqPush : IncomingPacketFactory<OnlinePushReqPush.ReqP
private suspend fun List<MsgInfo>.deco(
client: QQAndroidClient,
mapper: suspend ByteReadPacket.(msgInfo: MsgInfo) -> Sequence<Packet>
): Sequence<Packet> {
): List<Packet> {
return mapNotNull { msg ->
val successful = client.syncingController.onlinePushReqPushCacheList.addCache(
QQAndroidClient.MessageSvcSyncData.OnlinePushReqPushSyncId(
@ -70,7 +69,7 @@ internal object OnlinePushReqPush : IncomingPacketFactory<OnlinePushReqPush.ReqP
)
if (!successful) return@mapNotNull null
msg.vMsg.read { mapper(msg) }
}.asSequence().flatten()
}.asSequence().flatten().toList()
}
@ -79,7 +78,7 @@ internal object OnlinePushReqPush : IncomingPacketFactory<OnlinePushReqPush.ReqP
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): ReqPushDecoded {
val reqPushMsg = readUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req")
//bot.network.logger.debug { reqPushMsg._miraiContentToString() }
val packets: Sequence<Packet> = reqPushMsg.vMsgInfos.deco(bot.client) { msgInfo ->
val packets = reqPushMsg.vMsgInfos.deco(bot.client) { msgInfo ->
when (msgInfo.shMsgType.toInt()) {
732 -> {
val group = bot.getGroup(readUInt().toLong())
@ -123,8 +122,8 @@ internal object OnlinePushReqPush : IncomingPacketFactory<OnlinePushReqPush.ReqP
}
@Suppress("SpellCheckingInspection")
internal data class ReqPushDecoded(val request: OnlinePushPack.SvcReqPushMsg, val sequence: Sequence<Packet>) :
MultiPacketBySequence<Packet>(sequence), Packet.NoLog {
internal data class ReqPushDecoded(val request: OnlinePushPack.SvcReqPushMsg, val sequence: Collection<Packet>) :
MultiPacketImpl(sequence), Packet.NoLog {
override fun toString(): String {
return "OnlinePush.ReqPush.ReqPushDecoded"
}
@ -321,46 +320,7 @@ private object Transformers732 : Map<Int, Lambda732> by mapOf(
val message = tipsInfo.optBytesContent.decodeToString()
//机器人信息
if (tipsInfo.robotGroupOpt != 0) {
when (tipsInfo.robotGroupOpt) {
//添加
1 -> {
val dataList = message.parseToMessageDataList()
val invitor = dataList.first().let { messageData ->
group.getOrFail(messageData.data.toLong())
}
val member = dataList.last().let { messageData ->
group.newMember(
MemberInfoImpl(
uin = messageData.data.toLong(),
nick = messageData.text,
permission = MemberPermission.MEMBER,
remark = "",
nameCard = "",
specialTitle = "",
muteTimestamp = 0,
anonymousId = null,
isOfficialBot = true
)
).cast<NormalMember>().also {
group.members.delegate.add(it)
}
}
return@lambda732 sequenceOf(MemberJoinEvent.Invite(member, invitor))
}
//移除
2 -> {
message.parseToMessageDataList().first().let {
val member = group.getOrFail(it.data.toLong())
group.members.delegate.remove(member)
return@lambda732 sequenceOf(MemberLeaveEvent.Quit(member))
}
}
else -> {
bot.network.logger.debug { "Unknown robotGroupOpt ${tipsInfo.robotGroupOpt}, message=$message" }
return@lambda732 emptySequence()
}
}
TODO("removed")
} else when {
message.endsWith("群聊坦白说") -> {
val new = when (message) {
@ -586,7 +546,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
nick = body.msgAddFrdNotify.fuinNick,
remark = "",
)
)
).checkIsFriendImpl()
bot.friends.delegate.add(new)
return@lambda528 bot.getStranger(new.id)?.let {
bot.strangers.remove(new.id)
@ -614,7 +574,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
msg.msgFriendMsgSync.fuin
).sendAndExpect(bot)
response.friendList.firstOrNull()?.let {
val friend = Mirai.newFriend(bot, it.toMiraiFriendInfo())
val friend = Mirai.newFriend(bot, it.toMiraiFriendInfo()).checkIsFriendImpl()
bot.friends.delegate.add(friend)
packetList.add(FriendAddEvent(friend))
}
@ -625,11 +585,7 @@ internal object Transformers528 : Map<Long, Lambda528> by mapOf(
if (msg.msgGroupMsgSync != null) {
when (msg.msgGroupMsgSync.msgType) {
1, 2 -> {
bot.components[ContactUpdater].groupListModifyLock.withLock {
bot.createGroupForBot(Mirai.calculateGroupUinByGroupCode(msg.msgGroupMsgSync.grpCode))?.let {
packetList.add(BotJoinGroupEvent.Active(it))
}
}
TODO("removed")
}
}
}

View File

@ -158,7 +158,11 @@ internal fun <T : ProtoBuf> T.toByteArray(serializer: SerializationStrategy<T>):
/**
* load
*/
internal fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>): T {
internal fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, offset: Int = 0): T {
if (offset != 0) {
require(this.size >= offset) { "size < offset" }
return this.copyOfRange(offset, this.lastIndex).loadAs(deserializer)
}
return KtProtoBuf.decodeFromByteArray(deserializer, this)
}

View File

@ -11,6 +11,8 @@ package net.mamoe.mirai.internal.utils
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.utils.MiraiInternalApi
@Serializable
@ -20,6 +22,18 @@ internal data class MessageData(
val text: String,
)
internal fun MessageData.toMemberInfo() = MemberInfoImpl(
uin = data.toLong(),
nick = text,
permission = MemberPermission.MEMBER,
remark = "",
nameCard = "",
specialTitle = "",
muteTimestamp = 0,
anonymousId = null,
isOfficialBot = true
)
@Suppress("RegExpRedundantEscape")
internal val extraJsonPattern = Regex("<(\\{.*?\\})>")