Android GroupMessage support

This commit is contained in:
Him188 2020-01-25 21:20:13 +08:00
parent c42169a238
commit 6c2b435779
10 changed files with 329 additions and 161 deletions

View File

@ -0,0 +1,108 @@
package net.mamoe.mirai.qqandroid
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.FriendNameRemark
import net.mamoe.mirai.data.GroupInfo
import net.mamoe.mirai.data.PreviousNameList
import net.mamoe.mirai.data.Profile
import net.mamoe.mirai.message.data.ImageId
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.utils.*
import kotlin.coroutines.CoroutineContext
internal abstract class ContactImpl : Contact
internal class QQImpl(bot: Bot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), QQ {
override val bot: Bot by bot.unsafeWeakRef()
override suspend fun sendMessage(message: MessageChain) {
TODO("not implemented")
}
override suspend fun uploadImage(image: ExternalImage): ImageId {
TODO("not implemented")
}
override suspend fun queryProfile(): Profile {
TODO("not implemented")
}
override suspend fun queryPreviousNameList(): PreviousNameList {
TODO("not implemented")
}
override suspend fun queryRemark(): FriendNameRemark {
TODO("not implemented")
}
}
internal class MemberImpl(bot: Bot, group: Group, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), Member {
override val group: Group by group.unsafeWeakRef()
override val permission: MemberPermission
get() = TODO("not implemented")
override val bot: Bot by bot.unsafeWeakRef()
override suspend fun mute(durationSeconds: Int): Boolean {
TODO("not implemented")
}
override suspend fun unmute() {
TODO("not implemented")
}
override suspend fun queryProfile(): Profile {
TODO("not implemented")
}
override suspend fun queryPreviousNameList(): PreviousNameList {
TODO("not implemented")
}
override suspend fun queryRemark(): FriendNameRemark {
TODO("not implemented")
}
override suspend fun sendMessage(message: MessageChain) {
TODO("not implemented")
}
override suspend fun uploadImage(image: ExternalImage): ImageId {
TODO("not implemented")
}
}
@UseExperimental(MiraiInternalAPI::class)
internal class GroupImpl(bot: Bot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), Group {
override val internalId: GroupInternalId = GroupId(id).toInternalId()
override val owner: Member
get() = TODO("not implemented")
override val name: String
get() = TODO("not implemented")
override val announcement: String
get() = TODO("not implemented")
override val members: ContactList<Member> = ContactList(LockFreeLinkedList())
override fun getMember(id: Long): Member = members.delegate.filteringGetOrAdd({ it.id == id }, { MemberImpl(bot, this, coroutineContext, id) })
override suspend fun updateGroupInfo(): GroupInfo {
TODO("not implemented")
}
override suspend fun quit(): Boolean {
TODO("not implemented")
}
override val bot: Bot by bot.unsafeWeakRef()
override suspend fun sendMessage(message: MessageChain) {
TODO("not implemented")
}
override suspend fun uploadImage(image: ExternalImage): ImageId {
TODO("not implemented")
}
}

View File

@ -10,6 +10,7 @@ import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.utils.Context
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.coroutines.CoroutineContext
@ -27,30 +28,30 @@ internal abstract class QQAndroidBotBase constructor(
) : BotImpl<QQAndroidBotNetworkHandler>(account, configuration) {
val client: QQAndroidClient = QQAndroidClient(context, account, bot = @Suppress("LeakingThis") this as QQAndroidBot)
override val qqs: ContactList<QQ>
get() = TODO("not implemented")
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
override fun getQQ(id: Long): QQ {
TODO("not implemented")
return qqs.delegate.filteringGetOrAdd({ it.id == id }, { QQImpl(this, coroutineContext, id) })
}
override fun createNetworkHandler(coroutineContext: CoroutineContext): QQAndroidBotNetworkHandler {
return QQAndroidBotNetworkHandler(this as QQAndroidBot)
}
override val groups: ContactList<Group>
get() = TODO("not implemented")
override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
override suspend fun getGroup(id: GroupId): Group {
TODO("not implemented")
return groups.delegate.filteringGetOrAdd({ it.id == id.value }, { GroupImpl(this, coroutineContext, id.value) })
}
override suspend fun getGroup(internalId: GroupInternalId): Group {
TODO("not implemented")
internalId.toId().value.let { id ->
return groups.delegate.filteringGetOrAdd({ it.id == id }, { GroupImpl(this, coroutineContext, id) })
}
}
override suspend fun getGroup(id: Long): Group {
TODO("not implemented")
return groups.delegate.filteringGetOrAdd({ it.id == id }, { GroupImpl(this, coroutineContext, id) })
}
override suspend fun Image.getLink(): ImageLink {

View File

@ -4,6 +4,9 @@ import kotlinx.coroutines.*
import kotlinx.io.core.*
import kotlinx.io.pool.ObjectPool
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.Cancellable
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.qqandroid.QQAndroidBot
@ -12,7 +15,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.KnownPacketFactories
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket.LoginPacketResponse.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.SvcReqRegisterPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.*
import kotlin.coroutines.CoroutineContext
@ -48,7 +51,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
}
println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}")
SvcReqRegisterPacket(bot.client).sendAndExpect<SvcReqRegisterPacket.Response>()
StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>()
}
/**
@ -113,11 +116,26 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
if (PacketReceivedEvent(packet).broadcast().cancelled) {
return@parseIncomingPacket
}
// pass to listeners (attached by sendAndExpect).
packetListeners.forEach { listener ->
if (listener.filter(commandName, sequenceId) && packetListeners.remove(listener)) {
listener.complete(packet)
}
}
// broadcast
if (packet is Subscribable) {
if (packet is BroadcastControllable) {
if (packet.shouldBroadcast) packet.broadcast()
} else {
packet.broadcast()
}
if (packet is Cancellable && packet.cancelled) return@parseIncomingPacket
}
bot.logger.info(packet)
}
} finally {
println()

View File

@ -3,33 +3,56 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.toMessage
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MessageCommon
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgOnlinePush
import net.mamoe.mirai.utils.io.encodeToString
internal class OnlinePush {
internal object PbPushGroupMsg : PacketFactory<GroupMessage>("OnlinePush.PbPushGroupMsg") {
@UseExperimental(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GroupMessage {
// 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
discardExact(4)
println(ProtoBuf.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes()))
TODO()
}
val pbPushMsg = ProtoBuf.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes())
val message = MessageChain(initialCapacity = pbPushMsg.msg!!.msgBody!!.richText!!.elems!!.size)
/**
* 1 -> delimi
* 2 -> varint
* 3 -> delimi
* 4 -> varint
* 9 -> varint
*/
@Serializable
private class PbPushMsg(
val msg: MessageCommon.Msg
)
var extraInfo: ImMsgBody.ExtraInfo? = null
pbPushMsg.msg.msgBody!!.richText!!.elems!!.forEach {
when {
it.customFace != null -> message.add(Image(it.customFace.filePath))
it.text != null -> message.add(it.text.str.encodeToString().toMessage())
it.extraInfo != null -> extraInfo = it.extraInfo
}
}
val group = bot.getGroup(pbPushMsg.msg.msgHead!!.groupInfo!!.groupCode)
val flags = extraInfo?.flags?:0
return GroupMessage(
bot = bot,
group = group,
senderName = pbPushMsg.msg.msgHead.groupInfo!!.groupCard.encodeToString(),
sender = group.getMember(pbPushMsg.msg.msgHead.fromUin),
message = message,
permission = when{
flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
flags and 8 != 0 -> MemberPermission.OWNER
flags and 0 != 0 -> MemberPermission.MEMBER
else -> {
bot.logger.warning("判断群员权限失败")
MemberPermission.MEMBER
}
}
)
}
}
}

View File

@ -0,0 +1,131 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import kotlinx.io.core.ByteReadPacket
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.io.jceMap
import net.mamoe.mirai.qqandroid.network.io.jceStruct
import net.mamoe.mirai.qqandroid.network.protocol.jce.SvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.jce.writeUniRequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769.Oidb0x769
import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket
import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.utils.io.debugPrint
import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.utils.io.toReadPacket
import net.mamoe.mirai.utils.localIpAddress
@Suppress("EnumEntryName")
enum class RegPushReason {
appRegister,
createDefaultRegInfo,
fillRegProxy,
msfBoot,
msfByNetChange,
msfHeartTimeTooLong,
serverPush,
setOnlineStatus,
unknown
}
class StatSvc {
internal object Register : PacketFactory<Register.Response>("StatSvc.register") {
internal object Response : Packet {
override fun toString(): String = "Response(StatSvc.register)"
}
private const val subAppId = 537062845L
operator fun invoke(
client: QQAndroidClient,
regPushReason: RegPushReason = RegPushReason.appRegister
): OutgoingPacket = buildLoginOutgoingPacket(
client,
bodyType = 1,
extraData = client.wLoginSigInfo.d2.data,
key = client.wLoginSigInfo.d2Key
) { sequenceId ->
writeSsoPacket(
client, subAppId = subAppId, commandName = commandName,
extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId
) {
writeUniRequestPacket {
sServantName = "PushService"
sFuncName = "SvcReqRegister"
sBuffer = jceMap(
0,
"SvcReqRegister" to jceStruct(
0,
SvcReqRegister().apply {
cConnType = 0
lBid = 1 or 2 or 4
lUin = client.uin
iStatus = client.onlineStatus.id
bKikPC = 0 // 是否把 PC 踢下线
bKikWeak = 0
timeStamp = 0
// timeStamp = currentTimeSeconds // millis or seconds??
iLargeSeq = 1551 // ?
bOpenPush = 1
iLocaleID = 2052
bRegType =
(if (regPushReason == RegPushReason.appRegister ||
regPushReason == RegPushReason.fillRegProxy ||
regPushReason == RegPushReason.createDefaultRegInfo ||
regPushReason == RegPushReason.setOnlineStatus
) 0 else 1).toByte()
bIsSetStatus = if (regPushReason == RegPushReason.setOnlineStatus) 1 else 0
iOSVersion = client.device.version.sdk.toLong()
cNetType = if (client.networkType == NetworkType.WIFI) 1 else 0
vecGuid = client.device.guid
strDevName = client.device.model.encodeToString()
strDevType = client.device.model.encodeToString()
strOSVer = client.device.version.release.encodeToString()
uOldSSOIp = 0
uNewSSOIp = localIpAddress().split(".").foldIndexed(0L) { index: Int, acc: Long, s: String ->
acc or ((s.toLong() shl (index * 16)))
}
strVendorName = "MIUI"
strVendorOSName = "?ONEPLUS A5000_23_17"
// register 时还需要
/*
var44.uNewSSOIp = field_127445;
var44.uOldSSOIp = field_127444;
var44.strVendorName = ROMUtil.getRomName();
var44.strVendorOSName = ROMUtil.getRomVersion(20);
*/
bytes_0x769_reqbody = ProtoBuf.dump(
Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
rpt_config_list = listOf(
Oidb0x769.ConfigSeq(
type = 46,
version = 0
),
Oidb0x769.ConfigSeq(
type = 283,
version = 0
)
)
)
)
bSetMute = 0
}
)
)
}
this.writePacket(this.build().debugPrint("sso body"))
}
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
return Response
}
}
}

View File

@ -1,127 +0,0 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import kotlinx.io.core.ByteReadPacket
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.io.jceMap
import net.mamoe.mirai.qqandroid.network.io.jceStruct
import net.mamoe.mirai.qqandroid.network.protocol.jce.SvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.jce.writeUniRequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769.Oidb0x769
import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket
import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.utils.io.debugPrint
import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.utils.io.toReadPacket
import net.mamoe.mirai.utils.localIpAddress
@Suppress("EnumEntryName")
enum class RegPushReason {
appRegister,
createDefaultRegInfo,
fillRegProxy,
msfBoot,
msfByNetChange,
msfHeartTimeTooLong,
serverPush,
setOnlineStatus,
unknown
}
internal object SvcReqRegisterPacket : PacketFactory<SvcReqRegisterPacket.Response>("StatSvc.register") {
internal object Response : Packet
private const val subAppId = 537062845L
operator fun invoke(
client: QQAndroidClient,
regPushReason: RegPushReason = RegPushReason.appRegister
): OutgoingPacket = buildLoginOutgoingPacket(
client,
bodyType = 1,
extraData = client.wLoginSigInfo.d2.data,
key = client.wLoginSigInfo.d2Key
) { sequenceId ->
writeSsoPacket(
client, subAppId = subAppId, commandName = commandName,
extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId
) {
writeUniRequestPacket {
sServantName = "PushService"
sFuncName = "SvcReqRegister"
sBuffer = jceMap(
0,
"SvcReqRegister" to jceStruct(
0,
SvcReqRegister().apply {
cConnType = 0
lBid = 1 or 2 or 4
lUin = client.uin
iStatus = client.onlineStatus.id
bKikPC = 0 // 是否把 PC 踢下线
bKikWeak = 0
timeStamp = 0
// timeStamp = currentTimeSeconds // millis or seconds??
iLargeSeq = 1551 // ?
bOpenPush = 1
iLocaleID = 2052
bRegType =
(if (regPushReason == RegPushReason.appRegister ||
regPushReason == RegPushReason.fillRegProxy ||
regPushReason == RegPushReason.createDefaultRegInfo ||
regPushReason == RegPushReason.setOnlineStatus
) 0 else 1).toByte()
bIsSetStatus = if (regPushReason == RegPushReason.setOnlineStatus) 1 else 0
iOSVersion = client.device.version.sdk.toLong()
cNetType = if (client.networkType == NetworkType.WIFI) 1 else 0
vecGuid = client.device.guid
strDevName = client.device.model.encodeToString()
strDevType = client.device.model.encodeToString()
strOSVer = client.device.version.release.encodeToString()
uOldSSOIp = 0
uNewSSOIp = localIpAddress().split(".").foldIndexed(0L) { index: Int, acc: Long, s: String ->
acc or ((s.toLong() shl (index * 16)))
}
strVendorName = "MIUI"
strVendorOSName = "?ONEPLUS A5000_23_17"
// register 时还需要
/*
var44.uNewSSOIp = field_127445;
var44.uOldSSOIp = field_127444;
var44.strVendorName = ROMUtil.getRomName();
var44.strVendorOSName = ROMUtil.getRomVersion(20);
*/
bytes_0x769_reqbody = ProtoBuf.dump(
Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
rpt_config_list = listOf(
Oidb0x769.ConfigSeq(
type = 46,
version = 0
),
Oidb0x769.ConfigSeq(
type = 283,
version = 0
)
)
)
)
bSetMute = 0
}
)
)
}
this.writePacket(this.build().debugPrint("sso body"))
}
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
return Response
}
}

View File

@ -4,7 +4,7 @@ import java.lang.reflect.Field
import kotlin.reflect.full.allSuperclasses
actual fun Any.contentToStringReflectively(prefix: String): String {
actual fun Any.contentToStringReflectively(prefix: String, filter: ((name: String, value: Any?) -> Boolean)?): String {
val newPrefix = prefix
return (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + " {\n" +
this.allFieldsFromSuperClassesMatching { it.name.startsWith("net.mamoe.mirai") }
@ -14,6 +14,11 @@ actual fun Any.contentToStringReflectively(prefix: String): String {
prefix = newPrefix
) {
it.isAccessible = true
if (filter != null) {
kotlin.runCatching {
if (!filter(it.name, it.get(this))) return@joinToStringPrefixed ""
}
}
it.name + "=" + kotlin.runCatching {
val value = it.get(this)
if (value == this) "<this>"

View File

@ -242,7 +242,7 @@ object NullMessageChain : MessageChain {
override fun contains(sub: String): Boolean = error("accessing NullMessageChain")
override fun contains(element: Message): Boolean = error("accessing NullMessageChain")
override fun followedBy(tail: Message): MessageChain = error("accessing NullMessageChain")
override fun followedBy(tail: Message): MessageChain = tail.toChain()
override val size: Int get() = error("accessing NullMessageChain")
override fun containsAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")

View File

@ -222,7 +222,7 @@ fun Any?.contentToString(prefix: String = ""): String = when (this) {
}
}
expect fun Any.contentToStringReflectively(prefix: String = ""): String
expect fun Any.contentToStringReflectively(prefix: String = "", filter: ((String, Any?) -> Boolean)? = null): String
@Suppress("UNCHECKED_CAST")
fun ByteReadPacket.readProtoMap(length: Long = this.remaining): ProtoMap {

View File

@ -6,19 +6,28 @@ import kotlin.reflect.full.allSuperclasses
val FIELD_TRY_SET_ACCESSIBLE = Field::class.java.declaredMethods.firstOrNull { it.name == "trySetAccessible" }
actual fun Any.contentToStringReflectively(prefix: String): String {
actual fun Any.contentToStringReflectively(prefix: String, filter: ((name: String, value: Any?) -> Boolean)?): String {
val newPrefix = prefix + ProtoMap.indent
return (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + " {\n" +
this.allFieldsFromSuperClassesMatching { it.name.startsWith("net.mamoe.mirai") }
.distinctBy { it.name }
.filterNot { it.name.contains("$") || it.name == "Companion" || it.isSynthetic || it.name == "serialVersionUID" }
.filterNot { it.isEnumConstant }
.map {
FIELD_TRY_SET_ACCESSIBLE?.invoke(it, true) ?: kotlin.run { it.isAccessible = true }
val value = it.get(this)
if (filter != null) {
kotlin.runCatching {
if (!filter(it.name, value)) return@map it.name to FIELD_TRY_SET_ACCESSIBLE
}
}
it.name to value
}
.filterNot { it.second === FIELD_TRY_SET_ACCESSIBLE }
.joinToStringPrefixed(
prefix = newPrefix
) {
FIELD_TRY_SET_ACCESSIBLE?.invoke(it, true) ?: kotlin.run { it.isAccessible = true }
it.name + "=" + kotlin.runCatching {
val value = it.get(this)
) { (name, value) ->
"$name=" + kotlin.runCatching {
if (value == this) "<this>"
else value.contentToString(newPrefix)
}.getOrElse { "<!>" }