From 6a43bebac7b50888495158a4b3d0390b94d286d2 Mon Sep 17 00:00:00 2001 From: "Y. Z. Chen" <754097987@qq.com> Date: Tue, 19 Jan 2021 00:28:22 +0800 Subject: [PATCH 1/2] (typo) Fix a manual index typo --- docs/ConfiguringProjects.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ConfiguringProjects.md b/docs/ConfiguringProjects.md index c70ec706a..17c6845f8 100644 --- a/docs/ConfiguringProjects.md +++ b/docs/ConfiguringProjects.md @@ -24,7 +24,7 @@ - [A. 使用 Gradle](#a-使用-gradle) - [B. 使用 Maven](#b-使用-maven) -- [D. 下载 JAR 包](#c-下载-jar-包) +- [C. 下载 JAR 包](#c-下载-jar-包) ## A. 使用 Gradle @@ -143,4 +143,4 @@ dependencies { 在 [Jcenter](https://jcenter.bintray.com/net/mamoe/mirai-core-all/) 或 [阿里云代理仓库](https://maven.aliyun.com/repository/public/net/mamoe/mirai-core-all/) 下载指定版本的 `-all.jar` 文件,即包含 `mirai-core`,`mirai-core-api`,`mirai-core-utils` 和其他依赖。 -> [回到 Mirai 文档索引](README.md#jvm-平台-mirai-开发) \ No newline at end of file +> [回到 Mirai 文档索引](README.md#jvm-平台-mirai-开发) From 3a5d70741777ead4b94325d764db65306d36f49c Mon Sep 17 00:00:00 2001 From: sandtechnology <20417547+sandtechnology@users.noreply.github.com> Date: Wed, 20 Jan 2021 19:20:24 +0800 Subject: [PATCH 2/2] Support detect official bot, fix #439 (#810) * Support detect official bot, fix #439 * Fix wrong command name and add missed value * Adapt to changed invite event * Internal MessageData * Fix build * Fix wording --- .../src/commonMain/kotlin/data/MemberInfo.kt | 5 + mirai-core/src/commonMain/kotlin/MiraiImpl.kt | 2 +- .../kotlin/contact/MemberInfoImpl.kt | 8 +- .../network/QQAndroidBotNetworkHandler.kt | 4 + .../kotlin/network/QQAndroidClient.kt | 12 +++ .../network/protocol/data/proto/OIDB.kt | 92 +++++++++++++++++++ .../network/protocol/packet/PacketFactory.kt | 1 + .../protocol/packet/chat/TroopManagement.kt | 44 +++++++++ .../packet/chat/receive/OnlinePush.ReqPush.kt | 83 ++++++++++++++++- .../src/commonMain/kotlin/utils/string.kt | 23 +++++ 10 files changed, 270 insertions(+), 4 deletions(-) create mode 100644 mirai-core/src/commonMain/kotlin/utils/string.kt diff --git a/mirai-core-api/src/commonMain/kotlin/data/MemberInfo.kt b/mirai-core-api/src/commonMain/kotlin/data/MemberInfo.kt index b35227649..89bdc5d36 100644 --- a/mirai-core-api/src/commonMain/kotlin/data/MemberInfo.kt +++ b/mirai-core-api/src/commonMain/kotlin/data/MemberInfo.kt @@ -33,4 +33,9 @@ public interface MemberInfo : UserInfo { * 上次发言时间 秒 */ public val lastSpeakTimestamp: Int + + /** + * 是否为官方机器人 + */ + public val isOfficialBot: Boolean } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index 7e1ce6461..4873033e5 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -328,7 +328,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { nextUin = nextUin ).sendAndExpect(retry = 3) sequence += data.members.asSequence().map { troopMemberInfo -> - MemberInfoImpl(troopMemberInfo, ownerId) + MemberInfoImpl(bot.client, troopMemberInfo, ownerId) } nextUin = data.nextUin if (nextUin == 0L) { diff --git a/mirai-core/src/commonMain/kotlin/contact/MemberInfoImpl.kt b/mirai-core/src/commonMain/kotlin/contact/MemberInfoImpl.kt index caadc080d..873720bb2 100644 --- a/mirai-core/src/commonMain/kotlin/contact/MemberInfoImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/MemberInfoImpl.kt @@ -11,6 +11,7 @@ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.data.MemberInfo +import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopMemberInfo import net.mamoe.mirai.utils.currentTimeSeconds @@ -24,9 +25,11 @@ internal class MemberInfoImpl( override val muteTimestamp: Int, override val anonymousId: String?, override val joinTimestamp: Int = currentTimeSeconds().toInt(), - override var lastSpeakTimestamp: Int = 0 + override var lastSpeakTimestamp: Int = 0, + override val isOfficialBot: Boolean = false ) : MemberInfo, UserInfoImpl(uin, nick, remark) { constructor( + client: QQAndroidClient, jceInfo: StTroopMemberInfo, groupOwnerId: Long ) : this( @@ -43,6 +46,7 @@ internal class MemberInfoImpl( muteTimestamp = jceInfo.dwShutupTimestap?.toInt() ?: 0, anonymousId = null, joinTimestamp = jceInfo.dwJoinTime?.toInt() ?: 0, - lastSpeakTimestamp = jceInfo.dwLastSpeakTime?.toInt() ?: 0 + lastSpeakTimestamp = jceInfo.dwLastSpeakTime?.toInt() ?: 0, + isOfficialBot = client.groupConfig.isOfficialRobot(jceInfo.memberUin) ) } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt index ebbc2ea96..f8ea6081f 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt @@ -35,6 +35,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.network.protocol.packet.KnownPacketFactories.PacketFactoryIllegalStateException import net.mamoe.mirai.internal.network.protocol.packet.chat.GroupInfoImpl +import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetMsg import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList import net.mamoe.mirai.internal.network.protocol.packet.login.ConfigPushSvc @@ -386,6 +387,9 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo if (initGroupOk) { return } + logger.info { "Start syncing group config..." } + TroopManagement.GetTroopConfig(bot.client).sendAndExpect() + logger.info { "Successfully synced group config." } logger.info { "Start loading group list..." } val troopListData = FriendList.GetTroopListSimplify(bot.client) diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt index 3edb7624c..a5e6f682e 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt @@ -207,6 +207,18 @@ internal open class QQAndroidClient( */ val protocolVersion: Short = 8001 + internal val groupConfig: GroupConfig = GroupConfig() + + internal class GroupConfig { + var robotConfigVersion: Int = 0 + var aioKeyWordVersion: Int = 0 + var robotUinRangeList: List = emptyList() + + fun isOfficialRobot(uin: Long): Boolean { + return robotUinRangeList.any { range -> range.contains(uin) } + } + } + class MessageSvcSyncData { val firstNotify: AtomicBoolean = atomic(true) diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/OIDB.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/OIDB.kt index cc975285d..a964025f7 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/OIDB.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/OIDB.kt @@ -102,6 +102,98 @@ internal class Oidb0x5d2 : ProtoBuf { ) : ProtoBuf } +internal class Oidb0x496 { + @Serializable + internal class AioKeyword( + @JvmField @ProtoNumber(1) val keywords: List = emptyList(), + @JvmField @ProtoNumber(2) val rules: List = emptyList(), + @JvmField @ProtoNumber(3) val version: Int = 0 + ) : ProtoBuf + + @Serializable + internal class AioKeywordInfo( + @JvmField @ProtoNumber(1) val word: String = "", + @JvmField @ProtoNumber(2) val ruleId: Int = 0 + ) : ProtoBuf + + @Serializable + internal class AioKeywordRuleInfo( + @JvmField @ProtoNumber(1) val ruleId: Int = 0, + @JvmField @ProtoNumber(2) val startTime: Int = 0, + @JvmField @ProtoNumber(3) val endTime: Int = 0, + @JvmField @ProtoNumber(4) val postionFlag: Int = 0, + @JvmField @ProtoNumber(5) val matchGroupClass: List = emptyList(), + @JvmField @ProtoNumber(6) val version: Int = 0 + ) : ProtoBuf + + @Serializable + internal class GroupMsgConfig( + @JvmField @ProtoNumber(1) val boolUinEnable: Boolean = false, + @JvmField @ProtoNumber(2) val maxAioMsg: Int = 0, + @JvmField @ProtoNumber(3) val enableHelper: Int = 0, + @JvmField @ProtoNumber(4) val groupMaxNumber: Int = 0, + @JvmField @ProtoNumber(5) val nextUpdateTime: Int = 0 + ) : ProtoBuf + + @Serializable + internal class MsgSeqInfo( + @JvmField @ProtoNumber(1) val groupCode: Long = 0L, + @JvmField @ProtoNumber(2) val managerUinList: List = emptyList(), + @JvmField @ProtoNumber(3) val updateTime: Long = 0L, + @JvmField @ProtoNumber(4) val firstUnreadManagerMsgSeq: Long = 0L, + @JvmField @ProtoNumber(5) val uint64ManagerMsgSeq: List = emptyList() + ) : ProtoBuf + + @Serializable + internal class ReqBody( + @JvmField @ProtoNumber(1) val groupCode: Long = 0L, + @JvmField @ProtoNumber(2) val updateTime: Long = 0L, + @JvmField @ProtoNumber(3) val managerUinList: Long = 0L, + @JvmField @ProtoNumber(4) val firstUnreadManagerMsgSeq: Long = 0L, + @JvmField @ProtoNumber(5) val justFetchMsgConfig: Int = 0, + @JvmField @ProtoNumber(6) val type: Int = 0, + @JvmField @ProtoNumber(7) val version: Int = 0, + @JvmField @ProtoNumber(8) val aioKeywordVersion: Int = 0 + ) : ProtoBuf + + @Serializable + internal class Robot( + @JvmField @ProtoNumber(1) val version: Int = 0, + @JvmField @ProtoNumber(2) val uinRange: List = emptyList(), + @JvmField @ProtoNumber(3) val fireKeywords: List = emptyList(), + @JvmField @ProtoNumber(4) val startKeywords: List = emptyList(), + @JvmField @ProtoNumber(5) val endKeywords: List = emptyList(), + @JvmField @ProtoNumber(6) val sessionTimeout: Int = 0, + @JvmField @ProtoNumber(7) val subscribeCategories: List = emptyList() + ) : ProtoBuf + + @Serializable + internal class RobotSubscribeCategory( + @JvmField @ProtoNumber(1) val id: Int = 0, + @JvmField @ProtoNumber(2) val name: String = "", + @JvmField @ProtoNumber(3) val type: Int = 0, + @JvmField @ProtoNumber(4) val nextWording: String = "", + @JvmField @ProtoNumber(5) val nextContent: String = "" + ) : ProtoBuf + + @Serializable + internal class RspBody( + @JvmField @ProtoNumber(1) val msgSeqInfo: List = emptyList(), + @JvmField @ProtoNumber(2) val maxAioMsg: Long = 0L, + @JvmField @ProtoNumber(3) val maxPositionMsg: Long = 0L, + @JvmField @ProtoNumber(4) val msgGroupMsgConfig: GroupMsgConfig? = null, + @JvmField @ProtoNumber(5) val robotConfig: Robot? = null, + @JvmField @ProtoNumber(6) val aioKeywordConfig: AioKeyword? = null + ) : ProtoBuf + + @Serializable + internal class UinRange( + @JvmField @ProtoNumber(1) val startUin: Long = 0L, + @JvmField @ProtoNumber(2) val endUin: Long = 0L + ) : ProtoBuf +} + + @Serializable internal class Oidb0x8a0 : ProtoBuf { @Serializable diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt index 6bb1b194a..db8824666 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt @@ -142,6 +142,7 @@ internal object KnownPacketFactories { TroopManagement.EditSpecialTitle, TroopManagement.Mute, TroopManagement.GroupOperation, + TroopManagement.GetTroopConfig, // TroopManagement.GetGroupInfo, TroopManagement.EditGroupNametag, TroopManagement.Kick, diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt index d02fc28cd..cfd8a9847 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt @@ -147,6 +147,50 @@ internal class TroopManagement { } } + internal object GetTroopConfig : OutgoingPacketFactory("OidbSvc.0x496") { + class Response( + val success: Boolean + ) : Packet { + override fun toString(): String = "TroopManagement.GetTroopConfig.Response($success)" + } + + operator fun invoke( + client: QQAndroidClient + ): OutgoingPacket = buildOutgoingUniPacket(client) { + writeProtoBuf( + OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( + command = 1174, + result = 0, + serviceType = 0, + clientVersion = "android 8.4.18", + bodybuffer = Oidb0x496.ReqBody( + updateTime = 0, + firstUnreadManagerMsgSeq = 1, + version = client.groupConfig.robotConfigVersion, + aioKeywordVersion = client.groupConfig.aioKeyWordVersion, + type = 3 + ).toByteArray(Oidb0x496.ReqBody.serializer()) + ) + ) + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + readProtoBuf(OidbSso.OIDBSSOPkg.serializer()).let { pkg -> + pkg.bodybuffer.loadAs(Oidb0x496.RspBody.serializer()).let { data -> + bot.client.groupConfig.let { config -> + config.aioKeyWordVersion = data.aioKeywordConfig!!.version + config.robotConfigVersion = data.robotConfig!!.version + config.robotUinRangeList = data.robotConfig.uinRange.asSequence().map { range -> + LongRange(range.startUin, range.endUin) + }.toList() + } + } + + return Response(pkg.result == 0) + } + } + } + internal object Kick : OutgoingPacketFactory("OidbSvc.0x8a0_0") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val ret = this.readBytes() diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt index 387953ab1..d0175e43b 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt @@ -308,6 +308,87 @@ private object Transformers732 : Map by mapOf( }, // 传字符串信息 0x10 to lambda732 { group: GroupImpl, bot: QQAndroidBot -> + discardExact(1) + readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer()).let { body -> + when (body.optEnumType) { + 1 -> body.optMsgGraytips?.let { tipsInfo -> + 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().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() + } + } + } else when { + message.endsWith("群聊坦白说") -> { + val new = when (message) { + "管理员已关闭群聊坦白说" -> false + "管理员已开启群聊坦白说" -> true + else -> { + bot.network.logger.debug { "Unknown server confess talk messages $message" } + return@lambda732 emptySequence() + } + } + return@lambda732 sequenceOf( + GroupAllowConfessTalkEvent( + new, + !new, + group, + false + ) + ) + } + else -> { + bot.network.logger.debug { "Unknown server messages $message" } + return@lambda732 emptySequence() + } + } + } + else -> { + bot.network.logger.debug { + "Unknown Transformers732 0x10 optEnumType\noptEnumType=${body.optEnumType}\ncontent=${body._miraiContentToString()}" + } + return@lambda732 emptySequence() + } + } ?: return@lambda732 emptySequence() + } + /* val dataBytes = readBytes(26) when (dataBytes[0].toInt() and 0xFF) { @@ -371,7 +452,7 @@ private object Transformers732 : Map by mapOf( ) )*/ } - } + }*/ }, // recall diff --git a/mirai-core/src/commonMain/kotlin/utils/string.kt b/mirai-core/src/commonMain/kotlin/utils/string.kt new file mode 100644 index 000000000..863926c7d --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/utils/string.kt @@ -0,0 +1,23 @@ +package net.mamoe.mirai.internal.utils + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import net.mamoe.mirai.utils.MiraiInternalApi + +@Serializable +internal data class MessageData( + val data: String, + val cmd: Int, + val text: String +) + +@Suppress("RegExpRedundantEscape") +internal val extraJsonPattern = Regex("<(\\{.*?\\})>") + +@MiraiInternalApi +internal fun String.parseToMessageDataList(): Sequence { + return extraJsonPattern.findAll(this).filter { it.groups.size == 2 }.mapNotNull { result -> + Json.decodeFromString(MessageData.serializer(), result.groups[1]!!.value) + } +} +