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:
parent
497a458be4
commit
dc54679acb
mirai-core-api/src/commonMain/kotlin/event/events
mirai-core-utils/src/commonMain/kotlin
mirai-core/src/commonMain/kotlin
MiraiImpl.ktQQAndroidBot.kt
contact
message
network
Packet.kt
components
notice
BinaryMessageProcessor.ktGroupEventProcessorContext.ktGroupListNoticeProcessor.ktGroupMessageProcessor.ktSystemMessageProcessor.kt
decoders
protocol/packet
utils
@ -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]
|
||||
*/
|
||||
|
@ -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()
|
@ -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()
|
||||
}
|
@ -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),
|
||||
|
@ -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 }
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 -> {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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) {}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
"并描述此时机器人是否被踢出, 或是否有成员列表变更等动作."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
)
|
@ -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 -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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("<(\\{.*?\\})>")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user