Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Him188 2021-01-20 19:26:31 +08:00
commit aacd3c16e9
11 changed files with 272 additions and 6 deletions

View File

@ -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-开发)
> [回到 Mirai 文档索引](README.md#jvm-平台-mirai-开发)

View File

@ -33,4 +33,9 @@ public interface MemberInfo : UserInfo {
* 上次发言时间
*/
public val lastSpeakTimestamp: Int
/**
* 是否为官方机器人
*/
public val isOfficialBot: Boolean
}

View File

@ -328,7 +328,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
nextUin = nextUin
).sendAndExpect<FriendList.GetTroopMemberList.Response>(retry = 3)
sequence += data.members.asSequence().map { troopMemberInfo ->
MemberInfoImpl(troopMemberInfo, ownerId)
MemberInfoImpl(bot.client, troopMemberInfo, ownerId)
}
nextUin = data.nextUin
if (nextUin == 0L) {

View File

@ -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)
)
}

View File

@ -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<TroopManagement.GetTroopConfig.Response>()
logger.info { "Successfully synced group config." }
logger.info { "Start loading group list..." }
val troopListData = FriendList.GetTroopListSimplify(bot.client)

View File

@ -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<LongRange> = emptyList()
fun isOfficialRobot(uin: Long): Boolean {
return robotUinRangeList.any { range -> range.contains(uin) }
}
}
class MessageSvcSyncData {
val firstNotify: AtomicBoolean = atomic(true)

View File

@ -102,6 +102,98 @@ internal class Oidb0x5d2 : ProtoBuf {
) : ProtoBuf
}
internal class Oidb0x496 {
@Serializable
internal class AioKeyword(
@JvmField @ProtoNumber(1) val keywords: List<AioKeywordInfo> = emptyList(),
@JvmField @ProtoNumber(2) val rules: List<AioKeywordRuleInfo> = 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<Int> = 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<Long> = emptyList(),
@JvmField @ProtoNumber(3) val updateTime: Long = 0L,
@JvmField @ProtoNumber(4) val firstUnreadManagerMsgSeq: Long = 0L,
@JvmField @ProtoNumber(5) val uint64ManagerMsgSeq: List<Long> = 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<UinRange> = emptyList(),
@JvmField @ProtoNumber(3) val fireKeywords: List<String> = emptyList(),
@JvmField @ProtoNumber(4) val startKeywords: List<String> = emptyList(),
@JvmField @ProtoNumber(5) val endKeywords: List<String> = emptyList(),
@JvmField @ProtoNumber(6) val sessionTimeout: Int = 0,
@JvmField @ProtoNumber(7) val subscribeCategories: List<RobotSubscribeCategory> = 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<MsgSeqInfo> = 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

View File

@ -142,6 +142,7 @@ internal object KnownPacketFactories {
TroopManagement.EditSpecialTitle,
TroopManagement.Mute,
TroopManagement.GroupOperation,
TroopManagement.GetTroopConfig,
// TroopManagement.GetGroupInfo,
TroopManagement.EditGroupNametag,
TroopManagement.Kick,

View File

@ -147,6 +147,50 @@ internal class TroopManagement {
}
}
internal object GetTroopConfig : OutgoingPacketFactory<GetTroopConfig.Response>("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<Kick.Response>("OidbSvc.0x8a0_0") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
val ret = this.readBytes()

View File

@ -308,6 +308,87 @@ private object Transformers732 : Map<Int, Lambda732> 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<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()
}
}
} 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<Int, Lambda732> by mapOf(
)
)*/
}
}
}*/
},
// recall

View File

@ -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<MessageData> {
return extraJsonPattern.findAll(this).filter { it.groups.size == 2 }.mapNotNull { result ->
Json.decodeFromString(MessageData.serializer(), result.groups[1]!!.value)
}
}