Support TempMessage

This commit is contained in:
ryoii 2020-04-08 22:12:18 +08:00
parent fc8714de5f
commit 2053ab198c
11 changed files with 369 additions and 24 deletions

View File

@ -46,6 +46,7 @@ import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.LongMsg
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.qqandroid.utils.*
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.encodeToString
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
@ -327,6 +328,22 @@ internal abstract class QQAndroidBotBase constructor(
source.time
).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
}
is MessageSourceFromTempImpl,
is MessageSourceToTempImpl
-> network.run {
check(source.fromId == this@QQAndroidBotBase.id) {
"can only recall a message sent by bot"
}
source as MessageSourceToTempImpl
PbMessageSvc.PbMsgWithDraw.createForTempMessage(
bot.client,
source.target.group.id,
source.targetId,
source.sequenceId,
source.id,
source.time
).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
}
is OfflineMessageSource -> network.run {
when (source.kind) {
OfflineMessageSource.Kind.FRIEND -> {
@ -341,6 +358,19 @@ internal abstract class QQAndroidBotBase constructor(
source.time
).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
}
OfflineMessageSource.Kind.TEMP -> {
check(source.fromId == this@QQAndroidBotBase.id) {
"can only recall a message sent by bot"
}
PbMessageSvc.PbMsgWithDraw.createForTempMessage(
bot.client,
source.targetId, // groupUin
source.targetId, // memberUin
source.sequenceId,
source.id,
source.time
).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
}
OfflineMessageSource.Kind.GROUP -> {
PbMessageSvc.PbMsgWithDraw.createForGroupMessage(
bot.client,

View File

@ -11,6 +11,8 @@
package net.mamoe.mirai.qqandroid.contact
import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.launch
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.*
@ -25,10 +27,15 @@ import net.mamoe.mirai.event.events.MemberSpecialTitleChangeEvent
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.OfflineFriendImage
import net.mamoe.mirai.message.data.asMessageChain
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.message.MessageSourceToTempImpl
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopMemberInfo
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.utils.*
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmSynthetic
@ -42,6 +49,8 @@ internal class MemberImpl constructor(
) : Member() {
override val group: GroupImpl by group.unsafeWeakRef()
val lastMessageSequence: AtomicInt = atomic(-1)
// region QQ delegate
override val id: Long = qq.id
override val nick: String = qq.nick
@ -61,7 +70,23 @@ internal class MemberImpl constructor(
override suspend fun sendMessage(message: Message): MessageReceipt<Member> {
return sendMessageImpl(message).also {
logMessageSent(message)
} as MessageReceipt<Member>
}
}
private suspend fun sendMessageImpl(message: Message): MessageReceipt<Member> {
lateinit var source: MessageSourceToTempImpl
bot.network.run {
check(
MessageSvc.PbSendMsg.createToTemp(
bot.client,
this@MemberImpl,
message.asMessageChain()
) {
source = it
}.sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
) { "send message failed" }
}
return MessageReceipt(source, this, null)
}
@JvmSynthetic
@ -205,6 +230,15 @@ internal class MemberImpl constructor(
}
}
@OptIn(ExperimentalContracts::class)
internal fun Member.checkIsMemberImpl(): MemberImpl {
contract {
returns() implies (this@checkIsMemberImpl is MemberImpl)
}
check(this is MemberImpl) { "A Member instance is not instance of MemberImpl. Don't interlace two protocol implementations together!" }
return this
}
@OptIn(LowLevelAPI::class)
internal class MemberInfoImpl(
jceInfo: StTroopMemberInfo,

View File

@ -110,6 +110,71 @@ internal class MessageSourceFromFriendImpl(
}
}
internal class MessageSourceFromTempImpl(
override val bot: Bot,
private val msg: MsgComm.Msg
) : OnlineMessageSource.Incoming.FromTemp(), MessageSourceImpl {
override val sequenceId: Int get() = msg.msgHead.msgSeq
private val isRecalled: AtomicBoolean = atomic(false)
override var isRecalledOrPlanned: Boolean
get() = isRecalled.value
set(value) {
isRecalled.value = value
}
override val id: Int get() = msg.msgBody.richText.attr!!.random
override val time: Int get() = msg.msgHead.msgTime
override val originalMessage: MessageChain by lazy {
msg.toMessageChain(
bot,
groupIdOrZero = 0,
onlineSource = false
)
}
override val target: Bot get() = bot
override val sender: Member get() = with(msg.msgHead) {
bot.getGroup(c2cTmpMsgHead!!.groupUin)[fromUin]
}
private val elems by lazy {
msg.msgBody.richText.elems.toMutableList().also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
}
internal fun toJceDataImplForTemp(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
origSeqs = listOf(msg.msgHead.msgSeq),
senderUin = msg.msgHead.fromUin,
toUin = msg.msgHead.toUin,
flag = 1,
elems = msg.msgBody.richText.elems,
type = 0,
time = msg.msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr(
origUids = id.toULong().toLong()
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = msg.msgHead.fromUin, // qq
toUin = msg.msgHead.toUin, // group
msgType = msg.msgHead.msgType, // 82?
c2cCmd = msg.msgHead.c2cCmd,
msgSeq = msg.msgHead.msgSeq,
msgTime = msg.msgHead.msgTime,
msgUid = id.toULong().toLong(), // ok
// groupInfo = MsgComm.GroupInfo(groupCode = msg.msgHead.groupInfo.groupCode),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = elems
)
)
).toByteArray(MsgComm.Msg.serializer())
)
}
}
internal class MessageSourceFromGroupImpl(
override val bot: Bot,
private val msg: MsgComm.Msg
@ -168,7 +233,11 @@ internal class OfflineMessageSourceImplByMsg( // from other sources' originalMes
override val targetId: Long
get() = delegate.msgHead.groupInfo?.groupCode ?: delegate.msgHead.toUin
override val originalMessage: MessageChain by lazy {
delegate.toMessageChain(bot, delegate.msgHead.groupInfo?.groupCode ?: 0, false)
delegate.toMessageChain(bot,
groupIdOrZero = delegate.msgHead.groupInfo?.groupCode ?: 0,
onlineSource = false,
isTemp = delegate.msgHead.c2cTmpMsgHead != null
)
}
override val sequenceId: Int
get() = delegate.msgHead.msgSeq
@ -283,7 +352,63 @@ internal class MessageSourceToFriendImpl(
).toByteArray(MsgComm.Msg.serializer())
)
}
}
internal class MessageSourceToTempImpl(
override val sequenceId: Int,
override val id: Int,
override val time: Int,
override val originalMessage: MessageChain,
override val sender: Bot,
override val target: Member
) : OnlineMessageSource.Outgoing.ToTemp(), MessageSourceImpl {
override val bot: Bot
get() = sender
private val isRecalled: AtomicBoolean = atomic(false)
override var isRecalledOrPlanned: Boolean
get() = isRecalled.value
set(value) {
isRecalled.value = value
}
private val elems by lazy {
originalMessage.toRichTextElems(forGroup = false, withGeneralFlags = true)
}
fun toJceDataImplForTemp(): ImMsgBody.SourceMsg {
val messageUid: Long = sequenceId.toLong().shl(32) or id.toLong().and(0xffFFffFF)
return ImMsgBody.SourceMsg(
origSeqs = listOf(sequenceId),
senderUin = fromId,
toUin = targetId,
flag = 1,
elems = elems,
type = 0,
time = time,
pbReserve = SourceMsg.ResvAttr(
origUids = messageUid
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = fromId, // qq
toUin = targetId, // group
msgType = 9, // 82?
c2cCmd = 11,
msgSeq = sequenceId,
msgTime = time,
msgUid = messageUid, // ok
// groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = elems.toMutableList().also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
)
)
).toByteArray(MsgComm.Msg.serializer())
)
}
}
internal class MessageSourceToGroupImpl(

View File

@ -42,8 +42,10 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
is OfflineMessageSourceImplBySourceMsg -> elements.add(ImMsgBody.Elem(srcMsg = source.delegate))
is MessageSourceToFriendImpl -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceDataImplForFriend()))
is MessageSourceToGroupImpl -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceDataImplForGroup()))
is MessageSourceToTempImpl -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceDataImplForTemp()))
is MessageSourceFromFriendImpl -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceDataImplForFriend()))
is MessageSourceFromGroupImpl -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceDataImplForGroup()))
is MessageSourceFromTempImpl -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceDataImplForTemp()))
else -> error("unsupported MessageSource implementation: ${source::class.simpleName}")
}
}
@ -176,15 +178,15 @@ private val PB_RESERVE_FOR_DOUTU = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".h
private val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes()
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal fun MsgComm.Msg.toMessageChain(bot: Bot, groupIdOrZero: Long, onlineSource: Boolean): MessageChain {
internal fun MsgComm.Msg.toMessageChain(bot: Bot, groupIdOrZero: Long, onlineSource: Boolean, isTemp: Boolean = false): MessageChain {
val elements = this.msgBody.richText.elems
return buildMessageChain(elements.size + 1) {
if (onlineSource) {
if (groupIdOrZero != 0L) {
+MessageSourceFromGroupImpl(bot, this@toMessageChain)
} else {
+MessageSourceFromFriendImpl(bot, this@toMessageChain)
when {
isTemp -> +MessageSourceFromTempImpl(bot, this@toMessageChain)
groupIdOrZero != 0L -> +MessageSourceFromGroupImpl(bot, this@toMessageChain)
else -> +MessageSourceFromFriendImpl(bot, this@toMessageChain)
}
} else {
+OfflineMessageSourceImplByMsg(this@toMessageChain, bot)

View File

@ -20,6 +20,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.qqandroid.utils._miraiContentToString
import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
@ -73,6 +74,38 @@ internal class PbMessageSvc {
)
}
fun createForTempMessage(
client: QQAndroidClient,
groupUin: Long,
toUin: Long,
messageSequenceId: Int, // 56639
messageRandom: Int, // 921878719
time: Int
): OutgoingPacket = buildOutgoingUniPacket(client) {
writeProtoBuf(
MsgSvc.PbMsgWithDrawReq.serializer(),
MsgSvc.PbMsgWithDrawReq(
c2cWithDraw = listOf(
MsgSvc.PbC2CMsgWithDrawReq(
subCmd = 1,
msgInfo = listOf(
MsgSvc.PbC2CMsgWithDrawReq.MsgInfo(
fromUin = client.bot.id,
toUin = toUin,
msgSeq = messageSequenceId,
msgUid = 1000000000000000000L or messageRandom.toULong().toLong(),
msgTime = time.toLong(),
routingHead = MsgSvc.RoutingHead(
grpTmp = MsgSvc.GrpTmp(groupUin, toUin)
)
)
)
)
)
)
)
}
fun createForFriendMessage(
client: QQAndroidClient,
toUin: Long,

View File

@ -19,6 +19,7 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.data.MemberInfo
@ -27,14 +28,13 @@ import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.MemberJoinEvent
import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.TempMessage
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.GroupImpl
import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl
import net.mamoe.mirai.qqandroid.contact.checkIsQQImpl
import net.mamoe.mirai.qqandroid.message.MessageSourceToFriendImpl
import net.mamoe.mirai.qqandroid.message.MessageSourceToGroupImpl
import net.mamoe.mirai.qqandroid.message.toMessageChain
import net.mamoe.mirai.qqandroid.message.toRichTextElems
import net.mamoe.mirai.qqandroid.message.*
import net.mamoe.mirai.qqandroid.network.MultiPacketByIterable
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
@ -236,16 +236,38 @@ internal class MessageSvc {
} else return@mapNotNull null
}
}
84 -> { // 群验证
141 -> {
val tmpHead = msg.msgHead.c2cTmpMsgHead ?: return@mapNotNull null
val member = bot.getGroupByUinOrNull(tmpHead.groupUin)?.getOrNull(msg.msgHead.fromUin)
?: return@mapNotNull null
member.checkIsMemberImpl()
if (msg.msgHead.fromUin == bot.id || !bot.firstLoginSucceed) {
return@mapNotNull null
}
member.lastMessageSequence.loop { instant ->
if (msg.msgHead.msgSeq > instant) {
if (member.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
return@mapNotNull TempMessage(
member,
msg.toMessageChain(bot, groupIdOrZero = 0, onlineSource = true, isTemp = true)
)
}
} else return@mapNotNull null
}
}
84 -> { // 请求入群验证
bot.network.run {
NewContact.SystemMsgNewGroup(bot.client).sendWithoutExpect()
// 处理后要向服务器提交已阅,否则登陆时会重复收到事件
NewContact.Del(bot.client, msg.msgHead).sendWithoutExpect()
}
return@mapNotNull null
return@mapNotNull null
}
187 -> { // 好友验证
187 -> { // 请求加好友验证
bot.network.run {
NewContact.SystemMsgNewFriend(bot.client).sendWithoutExpect()
@ -360,6 +382,53 @@ internal class MessageSvc {
}
inline fun createToTemp(
client: QQAndroidClient,
member: Member,
message: MessageChain,
sourceCallback: (MessageSourceToTempImpl) -> Unit
): OutgoingPacket {
val source = MessageSourceToTempImpl(
id = Random.nextInt().absoluteValue,
sender = client.bot,
target = member,
time = currentTimeSeconds.toInt(),
sequenceId = client.atomicNextMessageSequenceId(),
originalMessage = message
)
sourceCallback(source)
return createToTemp(client, member.group.id, member.id, message, source)
}
/**
* 发送临时消息
*/
private fun createToTemp(
client: QQAndroidClient,
groupUin: Long,
toUin: Long,
message: MessageChain,
source: MessageSourceToTempImpl
): OutgoingPacket = buildOutgoingUniPacket(client) {
writeProtoBuf(
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
routingHead = MsgSvc.RoutingHead(
grpTmp = MsgSvc.GrpTmp(groupUin, toUin)
),
contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = message.toRichTextElems(forGroup = false, withGeneralFlags = true)
)
),
msgSeq = source.sequenceId,
msgRand = source.id,
syncCookie = SyncCookie(time = source.time.toULong().toLong()).toByteArray(SyncCookie.serializer())
)
)
}
inline fun createToGroup(
client: QQAndroidClient,
group: Group,

View File

@ -28,6 +28,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
*
* @see Group.sendMessage 发送群消息, 返回回执此对象
* @see QQ.sendMessage 发送群消息, 返回回执此对象
* @see Member.sendMessage 发送临时消息, 返回回执此对象
*
* @see MessageReceipt.sourceId id
* @see MessageReceipt.sourceTime 源时间
@ -45,7 +46,7 @@ actual constructor(
}
/**
* 发送目标, [Group] [QQ]
* 发送目标, [Group] [QQ] [Member]
*/
actual val target: C by target.unsafeWeakRef()

View File

@ -30,6 +30,7 @@ import kotlin.jvm.JvmSynthetic
*
* @see Group.sendMessage 发送群消息, 返回回执此对象
* @see QQ.sendMessage 发送群消息, 返回回执此对象
* @see Member.sendMessage 发送临时消息, 返回回执此对象
*
* @see MessageReceipt.sourceId id
* @see MessageReceipt.sourceTime 源时间
@ -45,7 +46,7 @@ expect open class MessageReceipt<out C : Contact>(
val source: OnlineMessageSource.Outgoing
/**
* 发送目标, [Group] [QQ]
* 发送目标, [Group] [QQ] [Member]
*/
val target: C

View File

@ -0,0 +1,22 @@
package net.mamoe.mirai.message
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.message.data.source
import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.unsafeWeakRef
class TempMessage(
sender: Member,
override val message: MessageChain
) : ContactMessage(), BroadcastControllable {
override val sender: Member by sender.unsafeWeakRef()
override val bot: Bot get() = sender.bot
override val subject: Member get() = sender
override val source: OnlineMessageSource.Incoming.FromTemp get() = message.source as OnlineMessageSource.Incoming.FromTemp
override fun toString(): String = "TempMessage(sender=${sender.id} from group(${sender.group.id}), message=$message)"
}

View File

@ -75,11 +75,11 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<OnlineMes
/**
* 发送目标.
* [OnlineMessageSource.Outgoing] 时为发信 [目标好友][QQ.id] [][Group.id]
* [OnlineMessageSource.Outgoing] 时为发信 [目标好友][QQ.id] [][Group.id] [临时消息][Member.id]
* [OnlineMessageSource.Incoming] 时为 [机器人][Bot.id]
* [OfflineMessageSource] 时为 [机器人][Bot.id], 发信 [目标好友][QQ.id] [][Group.id] (取决于 [OfflineMessageSource.kind])
* [OfflineMessageSource] 时为 [机器人][Bot.id], 发信 [目标好友][QQ.id] [][Group.id] [临时消息][Member.id] (取决于 [OfflineMessageSource.kind])
*/
abstract val targetId: Long // groupCode / friendUin
abstract val targetId: Long // groupCode / friendUin / memberUin
/**
* 原消息内容.
@ -127,10 +127,10 @@ sealed class OnlineMessageSource : MessageSource() {
abstract val target: Any
/**
* 消息主体. 群消息时为 [Group]. 好友消息时为 [QQ].
* 消息主体. 群消息时为 [Group]. 好友消息时为 [QQ], 临时消息为 [Member]
* 不论是机器人接收的消息还是发送的消息, 此属性都指向机器人能进行回复的目标.
*/
abstract val subject: Contact // Group or QQ
abstract val subject: Contact
/**
* [机器人主动发送消息][Contact.sendMessage] 产生的 [MessageSource]
@ -156,6 +156,15 @@ sealed class OnlineMessageSource : MessageSource() {
// final override fun toString(): String = "OnlineMessageSource.ToFriend(target=${target.id})"
}
abstract class ToTemp : Outgoing() {
companion object Key : Message.Key<ToTemp> {
override val typeName: String get() = "OnlineMessageSource.Outgoing.ToTemp"
}
abstract override val target: Member
final override val subject: Member get() = target
}
abstract class ToGroup : Outgoing() {
companion object Key : Message.Key<ToGroup> {
override val typeName: String get() = "OnlineMessageSource.Outgoing.ToGroup"
@ -193,6 +202,16 @@ sealed class OnlineMessageSource : MessageSource() {
// final override fun toString(): String = "OnlineMessageSource.FromFriend(from=${sender.id})"
}
abstract class FromTemp : Incoming() {
companion object Key : Message.Key<FromTemp> {
override val typeName: String
get() = "OnlineMessageSource.Incoming.FromTemp"
}
abstract override val sender: Member
final override val subject: Member get() = sender
}
abstract class FromGroup : Incoming() {
companion object Key : Message.Key<FromGroup> {
override val typeName: String
@ -215,10 +234,17 @@ inline fun MessageSource.isAboutGroup(): Boolean {
}
}
inline fun MessageSource.isAboutTemp(): Boolean {
return when(this) {
is OnlineMessageSource -> subject is Member
is OfflineMessageSource -> kind == OfflineMessageSource.Kind.TEMP
}
}
// inline for future removal
inline fun MessageSource.isAboutFriend(): Boolean {
return when (this) {
is OnlineMessageSource -> subject !is Group
is OnlineMessageSource -> subject !is Group && subject !is Member
is OfflineMessageSource -> kind == OfflineMessageSource.Kind.FRIEND
}
}
@ -267,7 +293,8 @@ abstract class OfflineMessageSource : MessageSource() {
enum class Kind {
GROUP,
FRIEND
FRIEND,
TEMP
}
/**

View File

@ -28,6 +28,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
*
* @see Group.sendMessage 发送群消息, 返回回执此对象
* @see QQ.sendMessage 发送群消息, 返回回执此对象
* @see Member.sendMessage 发送临时消息, 返回回执此对象
*
* @see MessageReceipt.sourceId id
* @see MessageReceipt.sourceTime 源时间
@ -45,7 +46,7 @@ actual constructor(
}
/**
* 发送目标, [Group] [QQ]
* 发送目标, [Group] [QQ] [Member]
*/
actual val target: C by target.unsafeWeakRef()