mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-05 17:15:08 +08:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
aacd3c16e9
@ -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-开发)
|
||||
|
@ -33,4 +33,9 @@ public interface MemberInfo : UserInfo {
|
||||
* 上次发言时间 秒
|
||||
*/
|
||||
public val lastSpeakTimestamp: Int
|
||||
|
||||
/**
|
||||
* 是否为官方机器人
|
||||
*/
|
||||
public val isOfficialBot: Boolean
|
||||
}
|
@ -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) {
|
||||
|
@ -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)
|
||||
)
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -142,6 +142,7 @@ internal object KnownPacketFactories {
|
||||
TroopManagement.EditSpecialTitle,
|
||||
TroopManagement.Mute,
|
||||
TroopManagement.GroupOperation,
|
||||
TroopManagement.GetTroopConfig,
|
||||
// TroopManagement.GetGroupInfo,
|
||||
TroopManagement.EditGroupNametag,
|
||||
TroopManagement.Kick,
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
23
mirai-core/src/commonMain/kotlin/utils/string.kt
Normal file
23
mirai-core/src/commonMain/kotlin/utils/string.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user