Skip to content

Commit a73c038

Browse files
Support detect official bot, fix mamoe#439
1 parent fce2ac4 commit a73c038

File tree

10 files changed

+260
-2
lines changed

10 files changed

+260
-2
lines changed

mirai-core-api/src/commonMain/kotlin/data/MemberInfo.kt

+1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ public interface MemberInfo : UserInfo {
2323
public val muteTimestamp: Int
2424

2525
public val anonymousId: String? get() = null
26+
public val isOfficialBot: Boolean
2627
}

mirai-core/src/commonMain/kotlin/MiraiImpl.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
273273
nextUin = nextUin
274274
).sendAndExpect<FriendList.GetTroopMemberList.Response>(retry = 3)
275275
sequence += data.members.asSequence().map { troopMemberInfo ->
276-
MemberInfoImpl(troopMemberInfo, ownerId)
276+
MemberInfoImpl(bot.client, troopMemberInfo, ownerId)
277277
}
278278
nextUin = data.nextUin
279279
if (nextUin == 0L) {

mirai-core/src/commonMain/kotlin/contact/MemberInfoImpl.kt

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package net.mamoe.mirai.internal.contact
1111

1212
import net.mamoe.mirai.contact.MemberPermission
1313
import net.mamoe.mirai.data.MemberInfo
14+
import net.mamoe.mirai.internal.network.QQAndroidClient
1415
import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopMemberInfo
1516

1617
internal class MemberInfoImpl(
@@ -22,8 +23,10 @@ internal class MemberInfoImpl(
2223
override val specialTitle: String,
2324
override val muteTimestamp: Int,
2425
override val anonymousId: String?,
26+
override val isOfficialBot: Boolean = false
2527
) : MemberInfo, UserInfoImpl(uin, nick, remark) {
2628
constructor(
29+
client: QQAndroidClient,
2730
jceInfo: StTroopMemberInfo,
2831
groupOwnerId: Long
2932
) : this(
@@ -39,5 +42,6 @@ internal class MemberInfoImpl(
3942
specialTitle = jceInfo.sSpecialTitle.orEmpty(),
4043
muteTimestamp = jceInfo.dwShutupTimestap?.toInt() ?: 0,
4144
anonymousId = null,
45+
isOfficialBot = client.groupConfig.isOfficialRobot(jceInfo.memberUin)
4246
)
4347
}

mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc
3535
import net.mamoe.mirai.internal.network.protocol.packet.*
3636
import net.mamoe.mirai.internal.network.protocol.packet.KnownPacketFactories.PacketFactoryIllegalStateException
3737
import net.mamoe.mirai.internal.network.protocol.packet.chat.GroupInfoImpl
38+
import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement
3839
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetMsg
3940
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
4041
import net.mamoe.mirai.internal.network.protocol.packet.login.ConfigPushSvc
@@ -380,6 +381,9 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
380381
if (initGroupOk) {
381382
return
382383
}
384+
logger.info { "Start getting group config..." }
385+
TroopManagement.GetTroopConfig(bot.client).sendAndExpect<TroopManagement.GetTroopConfig.Response>()
386+
logger.info { "Successfully synced group config..." }
383387

384388
logger.info { "Start loading group list..." }
385389
val troopListData = FriendList.GetTroopListSimplify(bot.client)

mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt

+12
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,18 @@ internal open class QQAndroidClient(
207207
*/
208208
val protocolVersion: Short = 8001
209209

210+
internal val groupConfig: GroupConfig = GroupConfig()
211+
212+
internal class GroupConfig {
213+
var robotConfigVersion: Int = 0
214+
var aioKeyWordVersion: Int = 0
215+
var robotUinRangeList: List<LongRange> = emptyList()
216+
217+
fun isOfficialRobot(uin: Long): Boolean {
218+
return robotUinRangeList.any { range -> range.contains(uin) }
219+
}
220+
}
221+
210222
class MessageSvcSyncData {
211223
val firstNotify: AtomicBoolean = atomic(true)
212224

mirai-core/src/commonMain/kotlin/network/protocol/data/proto/OIDB.kt

+92
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,98 @@ internal class Oidb0x5d2 : ProtoBuf {
102102
) : ProtoBuf
103103
}
104104

105+
internal class Oidb0x496 {
106+
@Serializable
107+
internal class AioKeyword(
108+
@JvmField @ProtoNumber(1) val keywords: List<AioKeywordInfo> = emptyList(),
109+
@JvmField @ProtoNumber(2) val rules: List<AioKeywordRuleInfo> = emptyList(),
110+
@JvmField @ProtoNumber(3) val version: Int = 0
111+
) : ProtoBuf
112+
113+
@Serializable
114+
internal class AioKeywordInfo(
115+
@JvmField @ProtoNumber(1) val word: String = "",
116+
@JvmField @ProtoNumber(2) val ruleId: Int = 0
117+
) : ProtoBuf
118+
119+
@Serializable
120+
internal class AioKeywordRuleInfo(
121+
@JvmField @ProtoNumber(1) val ruleId: Int = 0,
122+
@JvmField @ProtoNumber(2) val startTime: Int = 0,
123+
@JvmField @ProtoNumber(3) val endTime: Int = 0,
124+
@JvmField @ProtoNumber(4) val postionFlag: Int = 0,
125+
@JvmField @ProtoNumber(5) val matchGroupClass: List<Int> = emptyList(),
126+
@JvmField @ProtoNumber(6) val version: Int = 0
127+
) : ProtoBuf
128+
129+
@Serializable
130+
internal class GroupMsgConfig(
131+
@JvmField @ProtoNumber(1) val boolUinEnable: Boolean = false,
132+
@JvmField @ProtoNumber(2) val maxAioMsg: Int = 0,
133+
@JvmField @ProtoNumber(3) val enableHelper: Int = 0,
134+
@JvmField @ProtoNumber(4) val groupMaxNumber: Int = 0,
135+
@JvmField @ProtoNumber(5) val nextUpdateTime: Int = 0
136+
) : ProtoBuf
137+
138+
@Serializable
139+
internal class MsgSeqInfo(
140+
@JvmField @ProtoNumber(1) val groupCode: Long = 0L,
141+
@JvmField @ProtoNumber(2) val managerUinList: List<Long> = emptyList(),
142+
@JvmField @ProtoNumber(3) val updateTime: Long = 0L,
143+
@JvmField @ProtoNumber(4) val firstUnreadManagerMsgSeq: Long = 0L,
144+
@JvmField @ProtoNumber(5) val uint64ManagerMsgSeq: List<Long> = emptyList()
145+
) : ProtoBuf
146+
147+
@Serializable
148+
internal class ReqBody(
149+
@JvmField @ProtoNumber(1) val groupCode: Long = 0L,
150+
@JvmField @ProtoNumber(2) val updateTime: Long = 0L,
151+
@JvmField @ProtoNumber(3) val managerUinList: Long = 0L,
152+
@JvmField @ProtoNumber(4) val firstUnreadManagerMsgSeq: Long = 0L,
153+
@JvmField @ProtoNumber(5) val justFetchMsgConfig: Int = 0,
154+
@JvmField @ProtoNumber(6) val type: Int = 0,
155+
@JvmField @ProtoNumber(7) val version: Int = 0,
156+
@JvmField @ProtoNumber(8) val aioKeywordVersion: Int = 0
157+
) : ProtoBuf
158+
159+
@Serializable
160+
internal class Robot(
161+
@JvmField @ProtoNumber(1) val version: Int = 0,
162+
@JvmField @ProtoNumber(2) val uinRange: List<UinRange> = emptyList(),
163+
@JvmField @ProtoNumber(3) val fireKeywords: List<String> = emptyList(),
164+
@JvmField @ProtoNumber(4) val startKeywords: List<String> = emptyList(),
165+
@JvmField @ProtoNumber(5) val endKeywords: List<String> = emptyList(),
166+
@JvmField @ProtoNumber(6) val sessionTimeout: Int = 0,
167+
@JvmField @ProtoNumber(7) val subscribeCategories: List<RobotSubscribeCategory> = emptyList()
168+
) : ProtoBuf
169+
170+
@Serializable
171+
internal class RobotSubscribeCategory(
172+
@JvmField @ProtoNumber(1) val id: Int = 0,
173+
@JvmField @ProtoNumber(2) val name: String = "",
174+
@JvmField @ProtoNumber(3) val type: Int = 0,
175+
@JvmField @ProtoNumber(4) val nextWording: String = "",
176+
@JvmField @ProtoNumber(5) val nextContent: String = ""
177+
) : ProtoBuf
178+
179+
@Serializable
180+
internal class RspBody(
181+
@JvmField @ProtoNumber(1) val msgSeqInfo: List<MsgSeqInfo> = emptyList(),
182+
@JvmField @ProtoNumber(2) val maxAioMsg: Long = 0L,
183+
@JvmField @ProtoNumber(3) val maxPositionMsg: Long = 0L,
184+
@JvmField @ProtoNumber(4) val msgGroupMsgConfig: GroupMsgConfig? = null,
185+
@JvmField @ProtoNumber(5) val robotConfig: Robot? = null,
186+
@JvmField @ProtoNumber(6) val aioKeywordConfig: AioKeyword? = null
187+
) : ProtoBuf
188+
189+
@Serializable
190+
internal class UinRange(
191+
@JvmField @ProtoNumber(1) val startUin: Long = 0L,
192+
@JvmField @ProtoNumber(2) val endUin: Long = 0L
193+
) : ProtoBuf
194+
}
195+
196+
105197
@Serializable
106198
internal class Oidb0x8a0 : ProtoBuf {
107199
@Serializable

mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt

+1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ internal object KnownPacketFactories {
141141
TroopManagement.EditSpecialTitle,
142142
TroopManagement.Mute,
143143
TroopManagement.GroupOperation,
144+
TroopManagement.GetTroopConfig,
144145
// TroopManagement.GetGroupInfo,
145146
TroopManagement.EditGroupNametag,
146147
TroopManagement.Kick,

mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt

+43
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,49 @@ internal class TroopManagement {
147147
}
148148
}
149149

150+
internal object GetTroopConfig : OutgoingPacketFactory<GetTroopConfig.Response>("OidbSvc.0x899_9") {
151+
class Response(
152+
val success: Boolean
153+
) : Packet {
154+
override fun toString(): String = "TroopManagement.GetTroopConfig.Response($success)"
155+
}
156+
157+
operator fun invoke(
158+
client: QQAndroidClient
159+
): OutgoingPacket = buildOutgoingUniPacket(client) {
160+
writeProtoBuf(
161+
OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg(
162+
command = 1174,
163+
result = 0,
164+
serviceType = 0,
165+
bodybuffer = Oidb0x496.ReqBody(
166+
updateTime = 0,
167+
firstUnreadManagerMsgSeq = 1,
168+
version = client.groupConfig.robotConfigVersion,
169+
aioKeywordVersion = client.groupConfig.aioKeyWordVersion,
170+
type = 3
171+
).toByteArray(Oidb0x496.ReqBody.serializer())
172+
)
173+
)
174+
}
175+
176+
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
177+
readProtoBuf(OidbSso.OIDBSSOPkg.serializer()).let { pkg ->
178+
pkg.bodybuffer.loadAs(Oidb0x496.RspBody.serializer()).let { data ->
179+
bot.client.groupConfig.let { config ->
180+
config.aioKeyWordVersion = data.aioKeywordConfig!!.version
181+
config.robotConfigVersion = data.robotConfig!!.version
182+
config.robotUinRangeList = data.robotConfig.uinRange.asSequence().map { range ->
183+
LongRange(range.startUin, range.endUin)
184+
}.toList()
185+
}
186+
}
187+
188+
return Response(pkg.result == 0)
189+
}
190+
}
191+
}
192+
150193
internal object Kick : OutgoingPacketFactory<Kick.Response>("OidbSvc.0x8a0_0") {
151194
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
152195
val ret = this.readBytes()

mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt

+79-1
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,84 @@ private object Transformers732 : Map<Int, Lambda732> by mapOf(
294294
},
295295
// 传字符串信息
296296
0x10 to lambda732 { group: GroupImpl, bot: QQAndroidBot ->
297+
discardExact(1)
298+
readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer()).let { body ->
299+
when (body.optEnumType) {
300+
1 -> body.optMsgGraytips?.let { tipsInfo ->
301+
val message = tipsInfo.optBytesContent.decodeToString()
302+
//机器人信息
303+
if (tipsInfo.robotGroupOpt != 0) {
304+
when (tipsInfo.robotGroupOpt) {
305+
//添加
306+
1 -> {
307+
val dataList = message.parseToMessageDataList()
308+
val member = dataList.last().let { messageData ->
309+
group.newMember(
310+
MemberInfoImpl(
311+
uin = messageData.data.toLong(),
312+
nick = messageData.text,
313+
permission = MemberPermission.MEMBER,
314+
remark = "",
315+
nameCard = "",
316+
specialTitle = "",
317+
muteTimestamp = 0,
318+
anonymousId = null,
319+
isOfficialBot = true
320+
)
321+
).cast<NormalMember>().also {
322+
group.members.delegate.add(it)
323+
}
324+
}
325+
return@lambda732 sequenceOf(MemberJoinEvent.Invite(member))
326+
}
327+
//移除
328+
2 -> {
329+
message.parseToMessageDataList().first().let {
330+
val member = group.getOrFail(it.data.toLong())
331+
group.members.delegate.remove(member)
332+
return@lambda732 sequenceOf(MemberLeaveEvent.Quit(member))
333+
}
334+
}
335+
336+
else -> {
337+
bot.network.logger.debug { "Unknown robotGroupOpt ${tipsInfo.robotGroupOpt}, message=$message" }
338+
return@lambda732 emptySequence()
339+
}
340+
}
341+
} else when {
342+
message.endsWith("群聊坦白说") -> {
343+
val new = when (message) {
344+
"管理员已关闭群聊坦白说" -> false
345+
"管理员已开启群聊坦白说" -> true
346+
else -> {
347+
bot.network.logger.debug { "Unknown server confess talk messages $message" }
348+
return@lambda732 emptySequence()
349+
}
350+
}
351+
return@lambda732 sequenceOf(
352+
GroupAllowConfessTalkEvent(
353+
new,
354+
!new,
355+
group,
356+
false
357+
)
358+
)
359+
}
360+
else -> {
361+
bot.network.logger.debug { "Unknown server messages $message" }
362+
return@lambda732 emptySequence()
363+
}
364+
}
365+
}
366+
else -> {
367+
bot.network.logger.debug {
368+
"Unknown Transformers732 0x10 optEnumType\noptEnumType=${body.optEnumType}\ncontent=${body._miraiContentToString()}"
369+
}
370+
return@lambda732 emptySequence()
371+
}
372+
} ?: return@lambda732 emptySequence()
373+
}
374+
/*
297375
val dataBytes = readBytes(26)
298376
299377
when (dataBytes[0].toInt() and 0xFF) {
@@ -357,7 +435,7 @@ private object Transformers732 : Map<Int, Lambda732> by mapOf(
357435
)
358436
)*/
359437
}
360-
}
438+
}*/
361439
},
362440

363441
// recall
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package net.mamoe.mirai.internal.utils
2+
3+
import kotlinx.serialization.Serializable
4+
import kotlinx.serialization.json.Json
5+
import net.mamoe.mirai.utils.MiraiInternalApi
6+
7+
@Serializable
8+
data class MessageData(
9+
val data: String,
10+
val cmd: Int,
11+
val text: String
12+
)
13+
14+
@Suppress("RegExpRedundantEscape")
15+
internal val extraJsonPattern = Regex("<(\\{.*?\\})>")
16+
17+
@MiraiInternalApi
18+
internal fun String.parseToMessageDataList(): Sequence<MessageData> {
19+
return extraJsonPattern.findAll(this).filter { it.groups.size == 2 }.mapNotNull { result ->
20+
Json.decodeFromString(MessageData.serializer(), result.groups[1]!!.value)
21+
}
22+
}
23+

0 commit comments

Comments
 (0)