Commit 2b49a758 authored by Him188's avatar Him188

Reconstruct MessageSource, fix #197, #133

parent 9f7088d4
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.qqandroid
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.consumeEachBufferRange
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.io.*
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
import net.mamoe.mirai.qqandroid.utils.toReadPacket
import net.mamoe.mirai.utils.MiraiInternalAPI
import java.nio.ByteBuffer
@OptIn(MiraiInternalAPI::class)
@Suppress("DEPRECATION")
internal actual fun ByteReadChannel.toKotlinByteReadChannel(): kotlinx.coroutines.io.ByteReadChannel {
return object : kotlinx.coroutines.io.ByteReadChannel {
override val availableForRead: Int
get() = this@toKotlinByteReadChannel.availableForRead
override val isClosedForRead: Boolean
get() = this@toKotlinByteReadChannel.isClosedForRead
override val isClosedForWrite: Boolean
get() = this@toKotlinByteReadChannel.isClosedForWrite
@Suppress("DEPRECATION_ERROR", "OverridingDeprecatedMember")
override var readByteOrder: ByteOrder
get() = when (this@toKotlinByteReadChannel.readByteOrder) {
io.ktor.utils.io.core.ByteOrder.BIG_ENDIAN -> ByteOrder.BIG_ENDIAN
io.ktor.utils.io.core.ByteOrder.LITTLE_ENDIAN -> ByteOrder.LITTLE_ENDIAN
}
set(value) {
this@toKotlinByteReadChannel.readByteOrder = when (value) {
ByteOrder.BIG_ENDIAN -> io.ktor.utils.io.core.ByteOrder.BIG_ENDIAN
ByteOrder.LITTLE_ENDIAN -> io.ktor.utils.io.core.ByteOrder.LITTLE_ENDIAN
}
}
@Suppress("DEPRECATION_ERROR", "DEPRECATION", "OverridingDeprecatedMember")
override val totalBytesRead: Long
get() = this@toKotlinByteReadChannel.totalBytesRead
override fun cancel(cause: Throwable?): Boolean = this@toKotlinByteReadChannel.cancel(cause)
override suspend fun consumeEachBufferRange(visitor: ConsumeEachBufferVisitor) =
this@toKotlinByteReadChannel.consumeEachBufferRange(visitor)
override suspend fun discard(max: Long): Long = this@toKotlinByteReadChannel.discard(max)
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE")
@ExperimentalIoApi
override fun <R> lookAhead(visitor: LookAheadSession.() -> R): R {
return this@toKotlinByteReadChannel.lookAhead l@{
visitor(object : LookAheadSession {
override fun consumed(n: Int) {
return this@l.consumed(n)
}
override fun request(skip: Int, atLeast: Int): ByteBuffer? {
return this@l.request(skip, atLeast)
}
})
}
}
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE")
@ExperimentalIoApi
override suspend fun <R> lookAheadSuspend(visitor: suspend LookAheadSuspendSession.() -> R): R =
this@toKotlinByteReadChannel.lookAheadSuspend l@{
visitor(object : LookAheadSuspendSession {
override suspend fun awaitAtLeast(n: Int): Boolean {
return this@l.awaitAtLeast(n)
}
override fun consumed(n: Int) {
return this@l.consumed(n)
}
override fun request(skip: Int, atLeast: Int): ByteBuffer? {
return this@l.request(skip, atLeast)
}
})
}
override suspend fun read(min: Int, consumer: (ByteBuffer) -> Unit) =
this@toKotlinByteReadChannel.read(min, consumer)
override suspend fun readAvailable(dst: ByteBuffer): Int = this@toKotlinByteReadChannel.readAvailable(dst)
override suspend fun readAvailable(dst: ByteArray, offset: Int, length: Int): Int =
this@toKotlinByteReadChannel.readAvailable(dst, offset, length)
override suspend fun readAvailable(dst: IoBuffer): Int {
ByteArrayPool.useInstance {
val read = this@toKotlinByteReadChannel.readAvailable(it, 0, it.size)
dst.writeFully(it, 0, read)
return read
}
}
override suspend fun readBoolean(): Boolean = this@toKotlinByteReadChannel.readBoolean()
override suspend fun readByte(): Byte = this@toKotlinByteReadChannel.readByte()
override suspend fun readDouble(): Double = this@toKotlinByteReadChannel.readDouble()
override suspend fun readFloat(): Float = this@toKotlinByteReadChannel.readFloat()
override suspend fun readFully(dst: ByteBuffer): Int {
TODO("not implemented")
}
override suspend fun readFully(dst: ByteArray, offset: Int, length: Int) =
this@toKotlinByteReadChannel.readFully(dst, offset, length)
override suspend fun readFully(dst: IoBuffer, n: Int) {
ByteArrayPool.useInstance {
dst.writeFully(it, 0, this.readAvailable(it, 0, it.size))
}
}
override suspend fun readInt(): Int = this@toKotlinByteReadChannel.readInt()
override suspend fun readLong(): Long = this@toKotlinByteReadChannel.readLong()
override suspend fun readPacket(size: Int, headerSizeHint: Int): ByteReadPacket {
return this@toKotlinByteReadChannel.readPacket(size, headerSizeHint).readBytes().toReadPacket()
}
override suspend fun readRemaining(limit: Long, headerSizeHint: Int): ByteReadPacket {
return this@toKotlinByteReadChannel.readRemaining(limit, headerSizeHint).readBytes().toReadPacket()
}
@OptIn(ExperimentalIoApi::class)
@ExperimentalIoApi
override fun readSession(consumer: ReadSession.() -> Unit) {
@Suppress("DEPRECATION")
this@toKotlinByteReadChannel.readSession lambda@{
consumer(object : ReadSession {
override val availableForRead: Int
get() = this@lambda.availableForRead
override fun discard(n: Int): Int = this@lambda.discard(n)
override fun request(atLeast: Int): IoBuffer? {
val ioBuffer: io.ktor.utils.io.core.IoBuffer = this@lambda.request(atLeast) ?: return null
val buffer = IoBuffer.Pool.borrow()
val bytes = (ioBuffer as Input).readBytes()
buffer.writeFully(bytes)
return buffer
}
})
}
}
override suspend fun readShort(): Short = this@toKotlinByteReadChannel.readShort()
@Suppress("EXPERIMENTAL_OVERRIDE", "EXPERIMENTAL_API_USAGE")
@ExperimentalIoApi
override suspend fun readSuspendableSession(consumer: suspend SuspendableReadSession.() -> Unit) =
this@toKotlinByteReadChannel.readSuspendableSession l@{
consumer(object : SuspendableReadSession {
override val availableForRead: Int
get() = this@l.availableForRead
override suspend fun await(atLeast: Int): Boolean = this@l.await(atLeast)
override fun discard(n: Int): Int = this@l.discard(n)
override fun request(atLeast: Int): IoBuffer? {
@Suppress("DuplicatedCode") val ioBuffer: io.ktor.utils.io.core.IoBuffer =
this@l.request(atLeast) ?: return null
val buffer = IoBuffer.Pool.borrow()
val bytes = (ioBuffer as Input).readBytes()
buffer.writeFully(bytes)
return buffer
}
})
}
override suspend fun readUTF8Line(limit: Int): String? = this@toKotlinByteReadChannel.readUTF8Line(limit)
override suspend fun <A : Appendable> readUTF8LineTo(out: A, limit: Int): Boolean =
this@toKotlinByteReadChannel.readUTF8LineTo(out, limit)
}
}
\ No newline at end of file
...@@ -24,6 +24,7 @@ import kotlinx.serialization.UnstableDefault ...@@ -24,6 +24,7 @@ import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.json.int import kotlinx.serialization.json.int
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotImpl import net.mamoe.mirai.BotImpl
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
...@@ -35,8 +36,7 @@ import net.mamoe.mirai.message.data.* ...@@ -35,8 +36,7 @@ import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.contact.MemberInfoImpl import net.mamoe.mirai.qqandroid.contact.MemberInfoImpl
import net.mamoe.mirai.qqandroid.contact.QQImpl import net.mamoe.mirai.qqandroid.contact.QQImpl
import net.mamoe.mirai.qqandroid.contact.checkIsGroupImpl import net.mamoe.mirai.qqandroid.contact.checkIsGroupImpl
import net.mamoe.mirai.qqandroid.message.OnlineFriendImageImpl import net.mamoe.mirai.qqandroid.message.*
import net.mamoe.mirai.qqandroid.message.OnlineGroupImageImpl
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
...@@ -50,11 +50,22 @@ import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString ...@@ -50,11 +50,22 @@ import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString
import net.mamoe.mirai.qqandroid.utils.toReadPacket import net.mamoe.mirai.qqandroid.utils.toReadPacket
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import kotlin.collections.asSequence import kotlin.collections.asSequence
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.random.Random import kotlin.random.Random
@OptIn(ExperimentalContracts::class)
internal fun Bot.asQQAndroidBot(): QQAndroidBot {
contract {
returns() implies (this@asQQAndroidBot is QQAndroidBot)
}
return this as QQAndroidBot
}
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal class QQAndroidBot constructor( internal class QQAndroidBot constructor(
context: Context, context: Context,
...@@ -170,69 +181,58 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -170,69 +181,58 @@ internal abstract class QQAndroidBotBase constructor(
TODO("not implemented") TODO("not implemented")
} }
@Suppress("RemoveExplicitTypeArguments") // false positive
@ExperimentalMessageSource @ExperimentalMessageSource
override suspend fun recall(source: MessageSource) { override suspend fun recall(source: MessageSource) {
if (source.senderId != id && source.groupId != 0L) {
getGroup(source.groupId).checkBotPermissionOperator()
}
// println(source._miraiContentToString()) // println(source._miraiContentToString())
source.ensureSequenceIdAvailable()
network.run { check(source is MessageSourceImpl)
val response: PbMessageSvc.PbMsgWithDraw.Response =
if (source.groupId == 0L) { val response: PbMessageSvc.PbMsgWithDraw.Response = when (source) {
PbMessageSvc.PbMsgWithDraw.Friend( is MessageSourceToGroupImpl,
bot.client, is MessageSourceFromGroupImpl
source.senderId, -> {
source.sequenceId, val group = when (source) {
source.messageRandom, is MessageSourceToGroupImpl -> source.target
source.time is MessageSourceFromGroupImpl -> source.group
).sendAndExpect() else -> error("stub")
} else { }
MessageRecallEvent.GroupRecall( group.checkBotPermissionOperator()
bot, MessageRecallEvent.GroupRecall(
source.senderId, this,
source.id, source.senderId,
source.time.toInt(), source.id,
null, source.time,
getGroup(source.groupId) null,
).broadcast() group
PbMessageSvc.PbMsgWithDraw.Group( ).broadcast()
bot.client,
source.groupId, network.run {
PbMessageSvc.PbMsgWithDraw.createForGroupMessage(
bot.asQQAndroidBot().client,
group.id,
source.sequenceId, source.sequenceId,
source.messageRandom source.id
).sendAndExpect() ).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
} }
}
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.id}: $response" } is MessageSourceFromFriendImpl,
} is MessageSourceToFriendImpl
} -> network.run {
PbMessageSvc.PbMsgWithDraw.createForFriendMessage(
@OptIn(LowLevelAPI::class) bot.client,
override suspend fun _lowLevelRecallFriendMessage(friendId: Long, messageId: Long, time: Long) { source.senderId,
network.run { source.sequenceId,
val response: PbMessageSvc.PbMsgWithDraw.Response = source.id,
PbMessageSvc.PbMsgWithDraw.Friend(client, friendId, (messageId shr 32).toInt(), messageId.toInt(), time) source.time
.sendAndExpect() ).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
}
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${messageId}: $response" } else -> error("stub")
} }
}
@LowLevelAPI
override suspend fun _lowLevelRecallGroupMessage(groupId: Long, messageId: Long) {
network.run {
val response: PbMessageSvc.PbMsgWithDraw.Response =
PbMessageSvc.PbMsgWithDraw.Group(client, groupId, (messageId shr 32).toInt(), messageId.toInt())
.sendAndExpect()
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${messageId}: $response" } check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.id}: $response" }
}
} }
@LowLevelAPI @LowLevelAPI
@MiraiExperimentalAPI @MiraiExperimentalAPI
override suspend fun _lowLevelGetAnnouncements(groupId: Long, page: Int, amount: Int): GroupAnnouncementList { override suspend fun _lowLevelGetAnnouncements(groupId: Long, page: Int, amount: Int): GroupAnnouncementList {
...@@ -382,6 +382,7 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -382,6 +382,7 @@ internal abstract class QQAndroidBotBase constructor(
val group = getGroup(groupCode) val group = getGroup(groupCode)
val time = currentTimeSeconds val time = currentTimeSeconds
message.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
network.run { network.run {
val data = message.calculateValidationDataForGroup( val data = message.calculateValidationDataForGroup(
......
...@@ -25,7 +25,9 @@ import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent ...@@ -25,7 +25,9 @@ import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup import net.mamoe.mirai.qqandroid.message.MessageSourceToGroupImpl
import net.mamoe.mirai.qqandroid.message.ensureSequenceIdAvailable
import net.mamoe.mirai.qqandroid.message.firstIsInstanceOrNull
import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
...@@ -303,18 +305,17 @@ internal class GroupImpl( ...@@ -303,18 +305,17 @@ internal class GroupImpl(
|| imageCount >= 4 || imageCount >= 4
|| (event.message.any<QuoteReply>() || (event.message.any<QuoteReply>()
&& (imageCount != 0 || length > 100)) && (imageCount != 0 || length > 100))
) { ) return bot.lowLevelSendLongGroupMessage(this.id, event.message)
return bot.lowLevelSendLongGroupMessage(this.id, event.message)
}
msg = event.message msg = event.message
} else msg = message.asMessageChain() } else msg = message.asMessageChain()
msg.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
lateinit var source: MessageSourceFromSendGroup lateinit var source: MessageSourceToGroupImpl
bot.network.run { bot.network.run {
val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.ToGroup( val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.createToGroup(
bot.client, bot.client,
id, this@GroupImpl,
msg msg
) { ) {
source = it source = it
......
...@@ -22,10 +22,13 @@ import net.mamoe.mirai.event.broadcast ...@@ -22,10 +22,13 @@ import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.OfflineFriendImage import net.mamoe.mirai.message.data.OfflineFriendImage
import net.mamoe.mirai.message.data.QuoteReply
import net.mamoe.mirai.message.data.asMessageChain import net.mamoe.mirai.message.data.asMessageChain
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.message.MessageSourceToFriendImpl
import net.mamoe.mirai.qqandroid.message.ensureSequenceIdAvailable
import net.mamoe.mirai.qqandroid.message.firstIsInstanceOrNull
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopMemberInfo 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.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
...@@ -63,12 +66,13 @@ internal class MemberImpl constructor( ...@@ -63,12 +66,13 @@ internal class MemberImpl constructor(
if (event.isCancelled) { if (event.isCancelled) {
throw EventCancelledException("cancelled by FriendMessageSendEvent") throw EventCancelledException("cancelled by FriendMessageSendEvent")
} }
lateinit var source: MessageSource lateinit var source: MessageSourceToFriendImpl
event.message.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
bot.network.run { bot.network.run {
check( check(
MessageSvc.PbSendMsg.ToFriend( MessageSvc.PbSendMsg.createToFriend(
bot.client, bot.client,
id, this@MemberImpl,
event.message event.message
) { ) {
source = it source = it
......
...@@ -29,10 +29,13 @@ import net.mamoe.mirai.event.events.ImageUploadEvent ...@@ -29,10 +29,13 @@ import net.mamoe.mirai.event.events.ImageUploadEvent
import net.mamoe.mirai.event.events.MessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.OfflineFriendImage import net.mamoe.mirai.message.data.OfflineFriendImage
import net.mamoe.mirai.message.data.QuoteReply
import net.mamoe.mirai.message.data.asMessageChain import net.mamoe.mirai.message.data.asMessageChain
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.message.MessageSourceToFriendImpl
import net.mamoe.mirai.qqandroid.message.ensureSequenceIdAvailable
import net.mamoe.mirai.qqandroid.message.firstIsInstanceOrNull
import net.mamoe.mirai.qqandroid.network.highway.postImage import net.mamoe.mirai.qqandroid.network.highway.postImage
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
...@@ -80,12 +83,13 @@ internal class QQImpl( ...@@ -80,12 +83,13 @@ internal class QQImpl(
if (event.isCancelled) { if (event.isCancelled) {
throw EventCancelledException("cancelled by FriendMessageSendEvent") throw EventCancelledException("cancelled by FriendMessageSendEvent")
} }
lateinit var source: MessageSource event.message.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
lateinit var source: MessageSourceToFriendImpl
bot.network.run { bot.network.run {
check( check(
MessageSvc.PbSendMsg.ToFriend( MessageSvc.PbSendMsg.createToFriend(
bot.client, bot.client,
id, this@QQImpl,
event.message event.message
) { ) {
source = it source = it
......
...@@ -14,8 +14,8 @@ package net.mamoe.mirai.qqandroid.message ...@@ -14,8 +14,8 @@ package net.mamoe.mirai.qqandroid.message
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt import kotlinx.io.core.readUInt
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import net.mamoe.mirai.Bot
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.HummerCommelem import net.mamoe.mirai.qqandroid.network.protocol.data.proto.HummerCommelem
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
...@@ -42,9 +42,11 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B ...@@ -42,9 +42,11 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
if (this.any<QuoteReply>()) { if (this.any<QuoteReply>()) {
when (val source = this[QuoteReply].source) { when (val source = this[QuoteReply].source) {
is MessageSourceFromServer -> elements.add(ImMsgBody.Elem(srcMsg = source.delegate)) is OfflineMessageSourceImpl -> elements.add(ImMsgBody.Elem(srcMsg = source.delegate))
is MessageSourceFromMsg -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData())) is MessageSourceToFriendImpl -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceDataImplForFriend()))
is MessageSourceFromSend -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData())) is MessageSourceToGroupImpl -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceDataImplForGroup()))
is MessageSourceFromFriendImpl -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceDataImplForFriend()))
is MessageSourceFromGroupImpl -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceDataImplForGroup()))
else -> error("unsupported MessageSource implementation: ${source::class.simpleName}") else -> error("unsupported MessageSource implementation: ${source::class.simpleName}")
} }
} }
...@@ -122,19 +124,17 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B ...@@ -122,19 +124,17 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
.also { transformOneMessage(UNSUPPORTED_FLASH_MESSAGE_PLAIN) } .also { transformOneMessage(UNSUPPORTED_FLASH_MESSAGE_PLAIN) }
is AtAll -> elements.add(atAllData) is AtAll -> elements.add(atAllData)
is Face -> elements.add(ImMsgBody.Elem(face = it.toJceData())) is Face -> elements.add(ImMsgBody.Elem(face = it.toJceData()))
is QuoteReplyToSend -> { is QuoteReply -> {
if (forGroup) { if (forGroup) {
check(it is QuoteReplyToSend.ToGroup) { when (val source = it.source) {
"sending a quote to group using QuoteReplyToSend.ToFriend is prohibited" is OnlineMessageSource.Incoming.FromGroup -> {
} transformOneMessage(At(source.sender))
if (it.sender is Member) { transformOneMessage(PlainText(" "))
transformOneMessage(it.createAt()) }
} }
transformOneMessage(PlainText(" "))
} }
} }
is QuoteReply, // already transformed above is MessageSource, // mirai metadata only
is MessageSource, // mirai only
is RichMessage // already transformed above is RichMessage // already transformed above
-> { -> {
...@@ -173,28 +173,36 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B ...@@ -173,28 +173,36 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
private val PB_RESERVE_FOR_RICH_MESSAGE = private val PB_RESERVE_FOR_RICH_MESSAGE =
"08 09 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00".hexToBytes() "08 09 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00".hexToBytes()
@Suppress("SpellCheckingInspection")
private val PB_RESERVE_FOR_DOUTU = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".hexToBytes() private val PB_RESERVE_FOR_DOUTU = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".hexToBytes()
private val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes() private val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes()
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal fun MsgComm.Msg.toMessageChain(): MessageChain { internal fun MsgComm.Msg.toMessageChain(bot: Bot, isGroup: Boolean, addSource: Boolean): MessageChain {
val elements = this.msgBody.richText.elems val elements = this.msgBody.richText.elems
return buildMessageChain(elements.size + 1) { return buildMessageChain(elements.size + 1) {
+MessageSourceFromMsg(delegate = this@toMessageChain) if (addSource) {
elements.joinToMessageChain(this) if (isGroup) {
+MessageSourceFromGroupImpl(bot, this@toMessageChain)
} else {
+MessageSourceFromFriendImpl(bot, this@toMessageChain)
}
}
elements.joinToMessageChain(bot, this)
}.cleanupRubbishMessageElements() }.cleanupRubbishMessageElements()
} }
// These two functions have difference method signature, don't combine. // These two functions have difference method signature, don't combine.
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain { internal fun ImMsgBody.SourceMsg.toMessageChain(bot: Bot): MessageChain {
val elements = this.elems!! val elements = this.elems!!
return buildMessageChain(elements.size + 1) { return buildMessageChain(elements.size + 1) {
+MessageSourceFromServer(delegate = this@toMessageChain) +OfflineMessageSourceImpl(delegate = this@toMessageChain, bot = bot)
elements.joinToMessageChain(this) elements.joinToMessageChain(bot, this)
}.cleanupRubbishMessageElements() }.cleanupRubbishMessageElements()
} }
...@@ -228,7 +236,7 @@ private fun MessageChain.cleanupRubbishMessageElements(): MessageChain { ...@@ -228,7 +236,7 @@ private fun MessageChain.cleanupRubbishMessageElements(): MessageChain {
} }
internal inline fun <reified R> Iterable<*>.firstIsInstance(): R { internal inline fun <reified R> Iterable<*>.firstIsInstance(): R {
this.forEach { for (it in this) {
if (it is R) { if (it is R) {
return it return it
} }
...@@ -236,12 +244,21 @@ internal inline fun <reified R> Iterable<*>.firstIsInstance(): R { ...@@ -236,12 +244,21 @@ internal inline fun <reified R> Iterable<*>.firstIsInstance(): R {
throw NoSuchElementException("Collection contains no element matching the predicate.") throw NoSuchElementException("Collection contains no element matching the predicate.")
} }
internal inline fun <reified R> Iterable<*>.firstIsInstanceOrNull(): R? {
for (it in this) {
if (it is R) {
return it
}
}
return null
}
@OptIn(MiraiInternalAPI::class, LowLevelAPI::class) @OptIn(MiraiInternalAPI::class, LowLevelAPI::class)
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilder) { internal fun List<ImMsgBody.Elem>.joinToMessageChain(bot: Bot, message: MessageChainBuilder) {
// (this._miraiContentToString()) // (this._miraiContentToString())
this.forEach { this.forEach {
when { when {
it.srcMsg != null -> message.add(QuoteReply(MessageSourceFromServer(it.srcMsg))) it.srcMsg != null -> message.add(QuoteReply(OfflineMessageSourceImpl(it.srcMsg, bot)))
it.notOnlineImage != null -> message.add(OnlineFriendImageImpl(it.notOnlineImage)) it.notOnlineImage != null -> message.add(OnlineFriendImageImpl(it.notOnlineImage))
it.customFace != null -> message.add(OnlineGroupImageImpl(it.customFace)) it.customFace != null -> message.add(OnlineGroupImageImpl(it.customFace))
it.face != null -> message.add(Face(it.face.index)) it.face != null -> message.add(Face(it.face.index))
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
...@@ -40,7 +42,7 @@ internal class PbMessageSvc { ...@@ -40,7 +42,7 @@ internal class PbMessageSvc {
} }
// 12 1A 08 01 10 00 18 E7 C1 AD B8 02 22 0A 08 BF BA 03 10 BF 81 CB B7 03 2A 02 08 00 // 12 1A 08 01 10 00 18 E7 C1 AD B8 02 22 0A 08 BF BA 03 10 BF 81 CB B7 03 2A 02 08 00
fun Group( fun createForGroupMessage(
client: QQAndroidClient, client: QQAndroidClient,
groupCode: Long, groupCode: Long,
messageSequenceId: Int, // 56639 messageSequenceId: Int, // 56639
...@@ -71,12 +73,12 @@ internal class PbMessageSvc { ...@@ -71,12 +73,12 @@ internal class PbMessageSvc {
) )
} }
fun Friend( fun createForFriendMessage(
client: QQAndroidClient, client: QQAndroidClient,
toUin: Long, toUin: Long,
messageSequenceId: Int, // 56639 messageSequenceId: Int, // 56639
messageRandom: Int, // 921878719 messageRandom: Int, // 921878719
time: Long time: Int
): OutgoingPacket = buildOutgoingUniPacket(client) { ): OutgoingPacket = buildOutgoingUniPacket(client) {
val messageUid: Long = 262144L.shl(32) or messageRandom.toLong().and(0xffFFffFF) val messageUid: Long = 262144L.shl(32) or messageRandom.toLong().and(0xffFFffFF)
writeProtoBuf( writeProtoBuf(
...@@ -91,7 +93,7 @@ internal class PbMessageSvc { ...@@ -91,7 +93,7 @@ internal class PbMessageSvc {
toUin = toUin, toUin = toUin,
msgSeq = messageSequenceId, msgSeq = messageSequenceId,
msgUid = messageUid, msgUid = messageUid,
msgTime = time and 0xffffffff, msgTime = time.toULong().toLong(),
routingHead = MsgSvc.RoutingHead( routingHead = MsgSvc.RoutingHead(
c2c = MsgSvc.C2C( c2c = MsgSvc.C2C(
toUin = toUin toUin = toUin
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
*/ */
@file: OptIn(LowLevelAPI::class) @file: OptIn(LowLevelAPI::class)
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
...@@ -19,6 +20,7 @@ import kotlinx.io.core.discardExact ...@@ -19,6 +20,7 @@ import kotlinx.io.core.discardExact
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.event.events.BotJoinGroupEvent import net.mamoe.mirai.event.events.BotJoinGroupEvent
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
...@@ -29,8 +31,8 @@ import net.mamoe.mirai.message.data.MessageChain ...@@ -29,8 +31,8 @@ import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.GroupImpl import net.mamoe.mirai.qqandroid.contact.GroupImpl
import net.mamoe.mirai.qqandroid.contact.checkIsQQImpl import net.mamoe.mirai.qqandroid.contact.checkIsQQImpl
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendFriend import net.mamoe.mirai.qqandroid.message.MessageSourceToFriendImpl
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup import net.mamoe.mirai.qqandroid.message.MessageSourceToGroupImpl
import net.mamoe.mirai.qqandroid.message.toMessageChain import net.mamoe.mirai.qqandroid.message.toMessageChain
import net.mamoe.mirai.qqandroid.message.toRichTextElems import net.mamoe.mirai.qqandroid.message.toRichTextElems
import net.mamoe.mirai.qqandroid.network.MultiPacketByIterable import net.mamoe.mirai.qqandroid.network.MultiPacketByIterable
...@@ -82,6 +84,7 @@ internal class MessageSvc { ...@@ -82,6 +84,7 @@ internal class MessageSvc {
*/ */
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal object PbGetMsg : OutgoingPacketFactory<PbGetMsg.Response>("MessageSvc.PbGetMsg") { internal object PbGetMsg : OutgoingPacketFactory<PbGetMsg.Response>("MessageSvc.PbGetMsg") {
@Suppress("SpellCheckingInspection")
operator fun invoke( operator fun invoke(
client: QQAndroidClient, client: QQAndroidClient,
syncFlag: MsgSvc.SyncFlag = MsgSvc.SyncFlag.START, syncFlag: MsgSvc.SyncFlag = MsgSvc.SyncFlag.START,
...@@ -226,7 +229,7 @@ internal class MessageSvc { ...@@ -226,7 +229,7 @@ internal class MessageSvc {
if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) { if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
return@mapNotNull FriendMessage( return@mapNotNull FriendMessage(
friend, friend,
msg.toMessageChain() msg.toMessageChain(bot, isGroup = false, addSource = true)
) )
} }
} else return@mapNotNull null } else return@mapNotNull null
...@@ -289,34 +292,33 @@ internal class MessageSvc { ...@@ -289,34 +292,33 @@ internal class MessageSvc {
} }
} }
inline fun ToFriend( inline fun createToFriend(
client: QQAndroidClient, client: QQAndroidClient,
toUin: Long, qq: QQ,
message: MessageChain, message: MessageChain,
crossinline sourceCallback: (MessageSourceFromSendFriend) -> Unit crossinline sourceCallback: (MessageSourceToFriendImpl) -> Unit
): OutgoingPacket { ): OutgoingPacket {
val source = MessageSourceFromSendFriend( val source = MessageSourceToFriendImpl(
messageRandom = Random.nextInt().absoluteValue, id = Random.nextInt().absoluteValue,
senderId = client.uin, sender = client.bot,
toUin = toUin, target = qq,
time = currentTimeSeconds, time = currentTimeSeconds.toInt(),
groupId = 0,
sequenceId = client.atomicNextMessageSequenceId(), sequenceId = client.atomicNextMessageSequenceId(),
originalMessage = message originalMessage = message
) )
sourceCallback(source) sourceCallback(source)
return ToFriend(client, toUin, message, source) return createToFriend(client, qq.id, message, source)
} }
/** /**
* 发送好友消息 * 发送好友消息
*/ */
@Suppress("FunctionName") @Suppress("FunctionName")
private fun ToFriend( private fun createToFriend(
client: QQAndroidClient, client: QQAndroidClient,
toUin: Long, toUin: Long,
message: MessageChain, message: MessageChain,
source: MessageSourceFromSendFriend source: MessageSourceToFriendImpl
): OutgoingPacket = buildOutgoingUniPacket(client) { ): OutgoingPacket = buildOutgoingUniPacket(client) {
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes()) ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
...@@ -331,43 +333,42 @@ internal class MessageSvc { ...@@ -331,43 +333,42 @@ internal class MessageSvc {
) )
), ),
msgSeq = source.sequenceId, msgSeq = source.sequenceId,
msgRand = source.messageRandom, msgRand = source.id,
syncCookie = SyncCookie(time = source.time).toByteArray(SyncCookie.serializer()) syncCookie = SyncCookie(time = source.time.toULong().toLong()).toByteArray(SyncCookie.serializer())
// msgVia = 1 // msgVia = 1
) )
) )
} }
inline fun ToGroup( inline fun createToGroup(
client: QQAndroidClient, client: QQAndroidClient,
groupCode: Long, group: Group,
message: MessageChain, message: MessageChain,
sourceCallback: (MessageSourceFromSendGroup) -> Unit sourceCallback: (MessageSourceToGroupImpl) -> Unit
): OutgoingPacket { ): OutgoingPacket {
val source = MessageSourceFromSendGroup( val source = MessageSourceToGroupImpl(
messageRandom = Random.nextInt().absoluteValue, id = Random.nextInt().absoluteValue,
senderId = client.uin, sender = client.bot,
toUin = Group.calculateGroupUinByGroupCode(groupCode), target = group,
time = currentTimeSeconds, time = currentTimeSeconds.toInt(),
groupId = groupCode,
originalMessage = message//, originalMessage = message//,
// sourceMessage = message // sourceMessage = message
) )
sourceCallback(source) sourceCallback(source)
return ToGroup(client, groupCode, message, source) return createToGroup(client, group.id, message, source)
} }
/** /**
* 发送群消息 * 发送群消息
*/ */
@Suppress("FunctionName") @Suppress("FunctionName")
private fun ToGroup( private fun createToGroup(
client: QQAndroidClient, client: QQAndroidClient,
groupCode: Long, groupCode: Long,
message: MessageChain, message: MessageChain,
source: MessageSourceFromSendGroup source: MessageSourceToGroupImpl
): OutgoingPacket = buildOutgoingUniPacket(client) { ): OutgoingPacket = buildOutgoingUniPacket(client) {
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes()) ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
...@@ -384,7 +385,7 @@ internal class MessageSvc { ...@@ -384,7 +385,7 @@ internal class MessageSvc {
) )
), ),
msgSeq = client.atomicNextMessageSequenceId(), msgSeq = client.atomicNextMessageSequenceId(),
msgRand = source.messageRandom, msgRand = source.id,
syncCookie = EMPTY_BYTE_ARRAY, syncCookie = EMPTY_BYTE_ARRAY,
msgVia = 1 msgVia = 1
) )
......
...@@ -84,7 +84,7 @@ internal class OnlinePush { ...@@ -84,7 +84,7 @@ internal class OnlinePush {
return GroupMessage( return GroupMessage(
senderName = pbPushMsg.msg.msgHead.groupInfo.groupCard, senderName = pbPushMsg.msg.msgHead.groupInfo.groupCard,
sender = group[pbPushMsg.msg.msgHead.fromUin], sender = group[pbPushMsg.msg.msgHead.fromUin],
message = pbPushMsg.msg.toMessageChain(), message = pbPushMsg.msg.toMessageChain(bot, isGroup = true, addSource = true),
permission = when { permission = when {
flags and 16 != 0 -> MemberPermission.ADMINISTRATOR flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
flags and 8 != 0 -> MemberPermission.OWNER flags and 8 != 0 -> MemberPermission.OWNER
...@@ -369,13 +369,12 @@ internal class OnlinePush { ...@@ -369,13 +369,12 @@ internal class OnlinePush {
if (meta.authorUin == bot.id) { if (meta.authorUin == bot.id) {
null null
} else MessageRecallEvent.GroupRecall( } else MessageRecallEvent.GroupRecall(
bot, bot = bot,
meta.authorUin, authorId = meta.authorUin,
meta.seq.toLong().shl(32) or messageId = meta.msgRandom,
meta.msgRandom.toLong().and(0xffffffff), messageTime = meta.time,
meta.time, operator = memebr,
memebr, group = group
group
) )
} }
} }
......
...@@ -16,6 +16,7 @@ import kotlin.reflect.KProperty ...@@ -16,6 +16,7 @@ import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1 import kotlin.reflect.KProperty1
import kotlin.reflect.KType import kotlin.reflect.KType
private val indent: String = " ".repeat(4) private val indent: String = " ".repeat(4)
/** /**
...@@ -126,10 +127,12 @@ internal fun Any?._miraiContentToString(prefix: String = ""): String = when (thi ...@@ -126,10 +127,12 @@ internal fun Any?._miraiContentToString(prefix: String = ""): String = when (thi
internal expect fun KProperty1<*, *>.getValueAgainstPermission(receiver: Any): Any? internal expect fun KProperty1<*, *>.getValueAgainstPermission(receiver: Any): Any?
@Suppress("EXTENSION_SHADOWED_BY_MEMBER") @Suppress("EXTENSION_SHADOWED_BY_MEMBER")
private val KProperty1<*, *>.isConst: Boolean get() = false // on JVM, it will be resolved to member function private val KProperty1<*, *>.isConst: Boolean
get() = false // on JVM, it will be resolved to member function
@Suppress("EXTENSION_SHADOWED_BY_MEMBER") @Suppress("EXTENSION_SHADOWED_BY_MEMBER")
private val KClass<*>.isData: Boolean get() = false // on JVM, it will be resolved to member function private val KClass<*>.isData: Boolean
get() = false // on JVM, it will be resolved to member function
private fun Any.contentToStringReflectively( private fun Any.contentToStringReflectively(
prefix: String, prefix: String,
......
...@@ -7,7 +7,6 @@ import kotlinx.coroutines.io.ByteReadChannel ...@@ -7,7 +7,6 @@ import kotlinx.coroutines.io.ByteReadChannel
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.ExperimentalMessageSource
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource
...@@ -156,7 +155,6 @@ actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIA ...@@ -156,7 +155,6 @@ actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIA
* @see _lowLevelRecallFriendMessage 低级 API * @see _lowLevelRecallFriendMessage 低级 API
* @see _lowLevelRecallGroupMessage 低级 API * @see _lowLevelRecallGroupMessage 低级 API
*/ */
@ExperimentalMessageSource
@JvmSynthetic @JvmSynthetic
actual abstract suspend fun recall(source: MessageSource) actual abstract suspend fun recall(source: MessageSource)
......
...@@ -7,10 +7,14 @@ import kotlinx.coroutines.Job ...@@ -7,10 +7,14 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.JavaFriendlyAPI import net.mamoe.mirai.JavaFriendlyAPI
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.recallIn import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.data.ExperimentalMessageSource
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.message.data.QuoteReply
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.unsafeWeakRef import net.mamoe.mirai.utils.unsafeWeakRef
...@@ -31,7 +35,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef ...@@ -31,7 +35,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
actual open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSource::class) actual open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSource::class)
actual constructor( actual constructor(
actual val source: MessageSource, actual val source: OnlineMessageSource.Outgoing,
target: C, target: C,
private val botAsMember: Member? private val botAsMember: Member?
) { ) {
...@@ -51,96 +55,33 @@ actual constructor( ...@@ -51,96 +55,33 @@ actual constructor(
private val _isRecalled = atomic(false) private val _isRecalled = atomic(false)
/** @JavaFriendlyAPI
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次. @JvmName("quoteReply")
* fun __quoteReplyBlockingForJava__(message: Message): MessageReceipt<C> {
* @see Bot.recall return runBlocking { return@runBlocking quoteReply(message) }
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
*/
@OptIn(ExperimentalMessageSource::class)
actual suspend fun recall() {
@Suppress("BooleanLiteralArgument")
if (_isRecalled.compareAndSet(false, true)) {
when (val contact = target) {
is Group -> {
contact.bot.recall(source)
}
is QQ -> {
TODO()
}
else -> error("Unknown contact type")
}
} else error("message is already or planned to be recalled")
}
/**
* 在一段时间后撤回这条消息.. [recall] 或 [recallIn] 只能被调用一次.
*
* @param millis 延迟时间, 单位为毫秒
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
*/
actual fun recallIn(millis: Long): Job {
@Suppress("BooleanLiteralArgument")
if (_isRecalled.compareAndSet(false, true)) {
return when (val contact = target) {
is QQ,
is Group
-> contact.bot.recallIn(source, millis)
else -> error("Unknown contact type")
}
} else error("message is already or planned to be recalled")
}
/**
* [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable] 然后引用这条消息.
* @see MessageChain.quote 引用一条消息
*/
@OptIn(ExperimentalMessageSource::class)
actual open suspend fun quote(): QuoteReplyToSend {
this.source.ensureSequenceIdAvailable()
@OptIn(LowLevelAPI::class)
return _unsafeQuote()
}
/**
* 引用这条消息, 但不会 [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable].
* 在 sequenceId 可用前就发送这条消息则会导致一个异常.
* 当且仅当用于存储而不用于发送时使用这个方法.
*
* @see MessageChain.quote 引用一条消息
*/
@OptIn(ExperimentalMessageSource::class)
@LowLevelAPI
@Suppress("FunctionName")
actual fun _unsafeQuote(): QuoteReplyToSend {
return this.source.quote(botAsMember as? QQ)
}
/**
* 引用这条消息并回复.
* @see MessageChain.quote 引用一条消息
*/
@JvmSynthetic
actual suspend fun quoteReply(message: MessageChain) {
target.sendMessage(this.quote() + message)
} }
@JavaFriendlyAPI @JavaFriendlyAPI
@JvmName("quoteReply") @JvmName("quoteReply")
fun __quoteReplyBlockingForJava__(message: Message) { fun __quoteReplyBlockingForJava__(message: String): MessageReceipt<C> {
runBlocking { quoteReply(message) } return runBlocking { quoteReply(message) }
} }
@JavaFriendlyAPI @JavaFriendlyAPI
@JvmName("recall") @JvmName("recall")
fun __recallBlockingForJava__() { fun __recallBlockingForJava__() {
runBlocking { recall() } return runBlocking { return@runBlocking recall() }
}
@JavaFriendlyAPI
@JvmName("recall")
fun __recallInBlockingForJava__(timeMillis: Long): Job {
return recallIn(timeMillis = timeMillis)
} }
@JavaFriendlyAPI @JavaFriendlyAPI
@JvmName("quote") @JvmName("quote")
fun __quoteBlockingForJava__() { fun __quoteBlockingForJava__(): QuoteReply {
runBlocking { quote() } return this.quote()
} }
} }
\ No newline at end of file
...@@ -19,10 +19,7 @@ import kotlinx.coroutines.launch ...@@ -19,10 +19,7 @@ import kotlinx.coroutines.launch
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.ExperimentalMessageSource import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
...@@ -170,10 +167,8 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor { ...@@ -170,10 +167,8 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
* @throws PermissionDeniedException 当 [Bot] 无权限操作时 * @throws PermissionDeniedException 当 [Bot] 无权限操作时
* *
* @see Bot.recall (扩展函数) 接受参数 [MessageChain] * @see Bot.recall (扩展函数) 接受参数 [MessageChain]
* @see _lowLevelRecallFriendMessage 低级 API * @see MessageSource.recall
* @see _lowLevelRecallGroupMessage 低级 API
*/ */
@ExperimentalMessageSource
@JvmSynthetic @JvmSynthetic
abstract suspend fun recall(source: MessageSource) abstract suspend fun recall(source: MessageSource)
...@@ -222,20 +217,19 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor { ...@@ -222,20 +217,19 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
/** /**
* 撤回这条消息. * 撤回这条消息.
* 根据 [message] 内的 [MessageSource] 进行相关判断.
* *
* [Bot] 撤回自己的消息不需要权限. * [Bot] 撤回自己的消息不需要权限, 但需要在发出后 2 分钟内撤回.
* [Bot] 撤回群员的消息需要管理员权限. * [Bot] 撤回群员的消息需要管理员权限, 可在任意时间撤回.
* *
* @throws PermissionDeniedException 当 [Bot] 无权限操作时 * @throws PermissionDeniedException 当 [Bot] 无权限操作时
* @see Bot.recall * @see Bot.recall
*/ */
@JvmSynthetic @JvmSynthetic
suspend inline fun Bot.recall(message: MessageChain) = this.recall(message[MessageSource]) suspend inline fun Bot.recall(message: MessageChain) =
this.recall(message.source)
/** /**
* 在一段时间后撤回这条消息. * 在一段时间后撤回这个消息源所指代的消息.
* 将根据 [MessageSource.groupId] 判断消息是群消息还是好友消息.
* *
* @param millis 延迟的时间, 单位为毫秒 * @param millis 延迟的时间, 单位为毫秒
* @param coroutineContext 额外的 [CoroutineContext] * @param coroutineContext 额外的 [CoroutineContext]
......
...@@ -109,8 +109,7 @@ sealed class MessageRecallEvent : BotEvent { ...@@ -109,8 +109,7 @@ sealed class MessageRecallEvent : BotEvent {
* 消息 id. * 消息 id.
* @see MessageSource.id * @see MessageSource.id
*/ */
@ExperimentalMessageSource abstract val messageId: Int
abstract val messageId: Long
/** /**
* 原发送时间 * 原发送时间
...@@ -122,8 +121,7 @@ sealed class MessageRecallEvent : BotEvent { ...@@ -122,8 +121,7 @@ sealed class MessageRecallEvent : BotEvent {
*/ */
data class FriendRecall( data class FriendRecall(
override val bot: Bot, override val bot: Bot,
@ExperimentalMessageSource override val messageId: Int,
override val messageId: Long,
override val messageTime: Int, override val messageTime: Int,
/** /**
* 撤回操作人, 可能为 [Bot.uin] 或好友的 [QQ.id] * 撤回操作人, 可能为 [Bot.uin] 或好友的 [QQ.id]
...@@ -137,8 +135,7 @@ sealed class MessageRecallEvent : BotEvent { ...@@ -137,8 +135,7 @@ sealed class MessageRecallEvent : BotEvent {
data class GroupRecall( data class GroupRecall(
override val bot: Bot, override val bot: Bot,
override val authorId: Long, override val authorId: Long,
@ExperimentalMessageSource override val messageId: Int,
override val messageId: Long,
override val messageTime: Int, override val messageTime: Int,
/** /**
* 操作人. 为 null 时则为 [Bot] 操作. * 操作人. 为 null 时则为 [Bot] 操作.
......
...@@ -13,7 +13,6 @@ import kotlinx.coroutines.Job ...@@ -13,7 +13,6 @@ import kotlinx.coroutines.Job
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.data.* import net.mamoe.mirai.data.*
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.SinceMirai
import net.mamoe.mirai.utils.WeakRef import net.mamoe.mirai.utils.WeakRef
...@@ -69,21 +68,6 @@ interface LowLevelBotAPIAccessor { ...@@ -69,21 +68,6 @@ interface LowLevelBotAPIAccessor {
@LowLevelAPI @LowLevelAPI
suspend fun _lowLevelQueryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo> suspend fun _lowLevelQueryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo>
/**
* 撤回一条由机器人发送给好友的消息
* @param messageId [MessageSource.id]
*/
@MiraiExperimentalAPI("还未实现")
@LowLevelAPI
suspend fun _lowLevelRecallFriendMessage(friendId: Long, messageId: Long, time: Long)
/**
* 撤回一条群里的消息. 可以是机器人发送也可以是其他群员发送.
* @param messageId [MessageSource.id]
*/
@LowLevelAPI
suspend fun _lowLevelRecallGroupMessage(groupId: Long, messageId: Long)
/** /**
* 获取群公告列表 * 获取群公告列表
* @param page 页码 * @param page 页码
...@@ -131,20 +115,3 @@ interface LowLevelBotAPIAccessor { ...@@ -131,20 +115,3 @@ interface LowLevelBotAPIAccessor {
@MiraiExperimentalAPI @MiraiExperimentalAPI
suspend fun _lowLevelGetGroupActiveData(groupId: Long): GroupActiveData suspend fun _lowLevelGetGroupActiveData(groupId: Long): GroupActiveData
} }
/**
* 撤回一条群里的消息. 可以是机器人发送也可以是其他群员发送.
*/
@Suppress("FunctionName")
@MiraiExperimentalAPI
@LowLevelAPI
suspend fun LowLevelBotAPIAccessor._lowLevelRecallGroupMessage(
groupId: Long,
messageSequenceId: Int,
messageRandom: Int
) {
this._lowLevelRecallGroupMessage(
groupId,
messageSequenceId.toLong().shl(32) or messageRandom.toLong().and(0xFFFFFFFFL)
)
}
\ No newline at end of file
...@@ -13,6 +13,8 @@ import net.mamoe.mirai.Bot ...@@ -13,6 +13,8 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.message.data.MessageChain 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.getValue
import net.mamoe.mirai.utils.unsafeWeakRef import net.mamoe.mirai.utils.unsafeWeakRef
...@@ -23,6 +25,7 @@ class FriendMessage( ...@@ -23,6 +25,7 @@ class FriendMessage(
override val sender: QQ by sender.unsafeWeakRef() override val sender: QQ by sender.unsafeWeakRef()
override val bot: Bot get() = sender.bot override val bot: Bot get() = sender.bot
override val subject: QQ get() = sender override val subject: QQ get() = sender
override val source: OnlineMessageSource.Incoming.FromFriend get() = message.source as OnlineMessageSource.Incoming.FromFriend
override fun toString(): String = "FriendMessage(sender=${sender.id}, message=$message)" override fun toString(): String = "FriendMessage(sender=${sender.id}, message=$message)"
} }
...@@ -15,6 +15,8 @@ import net.mamoe.mirai.contact.Member ...@@ -15,6 +15,8 @@ import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.Event
import net.mamoe.mirai.message.data.MessageChain 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.getValue
import net.mamoe.mirai.utils.unsafeWeakRef import net.mamoe.mirai.utils.unsafeWeakRef
...@@ -34,6 +36,8 @@ class GroupMessage( ...@@ -34,6 +36,8 @@ class GroupMessage(
override val subject: Group get() = group override val subject: Group get() = group
override val source: OnlineMessageSource.Incoming.FromGroup get() = message.source as OnlineMessageSource.Incoming.FromGroup
inline fun Long.member(): Member = group[this] inline fun Long.member(): Member = group[this]
override fun toString(): String = override fun toString(): String =
......
...@@ -177,8 +177,7 @@ abstract class MessagePacketBase<out TSender : QQ, out TSubject : Contact> : Pac ...@@ -177,8 +177,7 @@ abstract class MessagePacketBase<out TSender : QQ, out TSubject : Contact> : Pac
@JvmName("reply2") @JvmName("reply2")
suspend inline fun MessageChain.quoteReply(): MessageReceipt<TSubject> = quoteReply(this) suspend inline fun MessageChain.quoteReply(): MessageReceipt<TSubject> = quoteReply(this)
@ExperimentalMessageSource open val source: OnlineMessageSource.Incoming get() = message.source as OnlineMessageSource.Incoming
inline fun MessageChain.quote(): QuoteReplyToSend = this.quote(sender)
// endregion // endregion
......
...@@ -7,14 +7,17 @@ ...@@ -7,14 +7,17 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.message package net.mamoe.mirai.message
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.recallIn import net.mamoe.mirai.recallIn
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
/** /**
...@@ -29,19 +32,17 @@ import kotlin.jvm.JvmSynthetic ...@@ -29,19 +32,17 @@ import kotlin.jvm.JvmSynthetic
* @see QQ.sendMessage 发送群消息, 返回回执(此对象) * @see QQ.sendMessage 发送群消息, 返回回执(此对象)
* *
* @see MessageReceipt.sourceId 源 id * @see MessageReceipt.sourceId 源 id
* @see MessageReceipt.sourceSequenceId 源序列号
* @see MessageReceipt.sourceTime 源时间 * @see MessageReceipt.sourceTime 源时间
*/ */
expect open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSource::class) constructor( expect open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSource::class) constructor(
source: MessageSource, source: OnlineMessageSource.Outgoing,
target: C, target: C,
botAsMember: Member? botAsMember: Member?
) { ) {
/** /**
* 指代发送出去的消息 * 指代发送出去的消息
*/ */
@ExperimentalMessageSource val source: OnlineMessageSource.Outgoing
val source: MessageSource
/** /**
* 发送目标, 为 [Group] 或 [QQ] * 发送目标, 为 [Group] 或 [QQ]
...@@ -52,66 +53,65 @@ expect open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSour ...@@ -52,66 +53,65 @@ expect open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSour
* 是否为发送给群的消息的回执 * 是否为发送给群的消息的回执
*/ */
val isToGroup: Boolean val isToGroup: Boolean
}
/** /**
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次. * 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
* *
* @see Bot.recall * @see Bot.recall
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
*/ */
suspend fun recall() suspend inline fun MessageReceipt<*>.recall() {
return target.bot.recall(source)
}
/** /**
* 在一段时间后撤回这条消息.. [recall] 或 [recallIn] 只能被调用一次. * 在一段时间后撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
* *
* @param millis 延迟时间, 单位为毫秒 * @param timeMillis 延迟时间, 单位为毫秒
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
*/ */
fun recallIn(millis: Long): Job inline fun MessageReceipt<*>.recallIn(
timeMillis: Long,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): Job = source.recallIn(timeMillis, coroutineContext)
/**
* [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable] 然后引用这条消息.
* @see MessageChain.quote 引用一条消息
*/
open suspend fun quote(): QuoteReplyToSend
/** /**
* 引用这条消息, 但不会 [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable]. * 引用这条消息.
* 在 sequenceId 可用前就发送这条消息则会导致一个异常. * @see MessageChain.quote 引用一条消息
* 当且仅当用于存储而不用于发送时使用这个方法. */
* @JvmSynthetic
* @see MessageChain.quote 引用一条消息 inline fun MessageReceipt<*>.quote(): QuoteReply = this.source.quote()
*/
@LowLevelAPI
@Suppress("FunctionName")
fun _unsafeQuote(): QuoteReplyToSend
/** /**
* 引用这条消息并回复. * 引用这条消息并回复.
* @see MessageChain.quote 引用一条消息 * @see MessageChain.quote 引用一条消息
*/ */
suspend fun quoteReply(message: MessageChain) @JvmSynthetic
suspend inline fun <C : Contact> MessageReceipt<C>.quoteReply(message: Message): MessageReceipt<C> {
@Suppress("UNCHECKED_CAST")
return target.sendMessage(this.quote() + message) as MessageReceipt<C>
} }
/** /**
* 获取源消息 [MessageSource.id] * 引用这条消息并回复.
* * @see MessageChain.quote 引用一条消息
* @see MessageSource.id
*/ */
@get:JvmSynthetic @JvmSynthetic
@ExperimentalMessageSource suspend inline fun <C : Contact> MessageReceipt<C>.quoteReply(message: String): MessageReceipt<C> {
inline val MessageReceipt<*>.sourceId: Long return this.quoteReply(message.toMessage())
get() = this.source.id }
/** /**
* 获取源消息 [MessageSource.sequenceId] * 获取源消息 [MessageSource.id]
* *
* @see MessageSource.sequenceId * @see MessageSource.id
*/ */
@get:JvmSynthetic @get:JvmSynthetic
@ExperimentalMessageSource inline val MessageReceipt<*>.sourceId: Int
inline val MessageReceipt<*>.sourceSequenceId: Int get() = this.source.id
get() = this.source.sequenceId
/** /**
* 获取源消息 [MessageSource.time] * 获取源消息 [MessageSource.time]
...@@ -119,15 +119,6 @@ inline val MessageReceipt<*>.sourceSequenceId: Int ...@@ -119,15 +119,6 @@ inline val MessageReceipt<*>.sourceSequenceId: Int
* @see MessageSource.time * @see MessageSource.time
*/ */
@get:JvmSynthetic @get:JvmSynthetic
@ExperimentalMessageSource inline val MessageReceipt<*>.sourceTime: Int
inline val MessageReceipt<*>.sourceTime: Long
get() = this.source.time get() = this.source.time
suspend inline fun MessageReceipt<*>.quoteReply(message: Message) {
return this.quoteReply(message.asMessageChain())
}
suspend inline fun MessageReceipt<*>.quoteReply(message: String) {
return this.quoteReply(message.toMessage().asMessageChain())
}
...@@ -9,11 +9,21 @@ ...@@ -9,11 +9,21 @@
@file:JvmMultifileClass @file:JvmMultifileClass
@file:JvmName("MessageUtils") @file:JvmName("MessageUtils")
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.ContactMessage
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.recallIn
import net.mamoe.mirai.utils.LazyProperty import net.mamoe.mirai.utils.LazyProperty
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.SinceMirai
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
...@@ -33,108 +43,189 @@ annotation class ExperimentalMessageSource ...@@ -33,108 +43,189 @@ annotation class ExperimentalMessageSource
* *
* @see Bot.recall 撤回一条消息 * @see Bot.recall 撤回一条消息
* @see MessageSource.quote 引用这条消息, 创建 [MessageChain] * @see MessageSource.quote 引用这条消息, 创建 [MessageChain]
*
* @see OnlineMessageSource 在线消息的 [MessageSource]
* @see OfflineMessageSource 离线消息的 [MessageSource]
*/ */
@ExperimentalMessageSource @SinceMirai("0.33.0")
interface MessageSource : Message, MessageMetadata { sealed class MessageSource : Message, MessageMetadata {
companion object Key : Message.Key<MessageSource> companion object Key : Message.Key<MessageSource>
/** /**
* 在 Mirai 中使用的 id. * 所属 [Bot]
* 高 32 位为 [sequenceId],
* 低 32 位为 [messageRandom]
*
* @see ensureSequenceIdAvailable 确保 sequenceId 可用
*/ */
val id: Long abstract val bot: Bot
/** /**
* 等待 [sequenceId] 获取, 确保其可用. * 消息 id.
*
* 这个方法 3 秒超时, 抛出 [IllegalStateException], 则表明原消息发送失败.
*/ */
suspend fun ensureSequenceIdAvailable() abstract val id: Int // random
/** /**
* 发送时间, 单位为秒. 撤回好友消息时可能需要 * 发送时间, 单位为秒. 撤回好友消息时可能需要
*/ */
val time: Long abstract val time: Int
/** /**
* 发送人. 可以为机器人自己 (`BotAsQQ`, `BotAsMember`) * 发送人. 可能为机器人自己, 好友的 id, 或群 id
*/ */
val senderId: Long abstract val senderId: Long
/** /**
* 消息发送对象, 可以为一个群的 `uin` (非 `id`)或一个好友, 或机器人自己 * 发送目标. 可能为机器人自己, 好友的 id, 或群 id
*/ */
val toUin: Long abstract val targetId: Long // groupCode / friendUin
/** /**
* 当群消息时为群 id, [Group.id], 好友消息时为 0 * 原消息内容.
*/ */
val groupId: Long @LazyProperty
abstract val originalMessage: MessageChain
final override fun toString(): String = ""
}
// ONLINE
/**
* 在线消息的 [MessageSource].
* 拥有对象化的 [sender], [target], 也可以直接 [recall] 和 [quote]
*
* ### 来源
* **必定是一个发出去的消息或接收到的消息的 [MessageChain] 中的一个元数据 [MessageMetadata].**
*
* #### 机器人主动发送消息
* 当机器人 [主动发出消息][Member.sendMessage], 将会得到一个 [消息回执][MessageReceipt].
* 此回执的 [消息源][MessageReceipt.source] 即为一个 [外向消息源][OnlineMessageSource.Outgoing], 代表着刚刚发出的那条消息的来源.
*
* #### 机器人接受消息
* 当机器人接收一条消息 [ContactMessage], 这条消息包含一个 [内向消息源][OnlineMessageSource.Incoming], 代表着接收到的这条消息的来源.
*/
@SinceMirai("0.33.0")
sealed class OnlineMessageSource : MessageSource() {
/** /**
* 原消息内容 * 消息发送人. 可能为 [机器人][Bot] 或 [好友][QQ] 或 [群员][Member].
* 即类型必定为 [Bot], [QQ] 或 [Member]
*/ */
@LazyProperty abstract val sender: Any
val originalMessage: MessageChain
/** /**
* 固定返回空字符串 ("") * 消息发送目标. 可能为 [机器人][Bot] 或 [好友][QQ] 或 [群][Group].
* 即类型必定为 [Bot], [QQ] 或 [Group]
*/ */
override fun toString(): String abstract val target: Any
/**
* 消息主体. 群消息时为 [Group]. 好友消息时为 [QQ].
* 不论是机器人接收的消息还是发送的消息, 此属性都指向机器人能进行回复的目标.
*/
abstract val subject: Contact // Group or QQ
/**
* 由 [机器人主动发送消息][Contact.sendMessage] 产生的 [MessageSource]
*/
sealed class Outgoing : OnlineMessageSource() {
abstract override val sender: Bot
abstract override val target: Contact
final override val senderId: Long get() = sender.id
final override val targetId: Long get() = target.id
abstract class ToFriend : Outgoing() {
abstract override val target: QQ
final override val subject: QQ get() = target
// final override fun toString(): String = "OnlineMessageSource.ToFriend(target=${target.id})"
}
abstract class ToGroup : Outgoing() {
abstract override val target: Group
final override val subject: Group get() = target
// final override fun toString(): String = "OnlineMessageSource.ToGroup(group=${target.id})"
}
}
/**
* 接收到的一条消息的 [MessageSource]
*/
sealed class Incoming : OnlineMessageSource() {
abstract override val sender: QQ // out QQ
abstract override val target: Bot
final override val senderId: Long get() = sender.id
final override val targetId: Long get() = target.id
abstract class FromFriend : Incoming() {
abstract override val sender: QQ
final override val subject: QQ get() = sender
// final override fun toString(): String = "OnlineMessageSource.FromFriend(from=${sender.id})"
}
abstract class FromGroup : Incoming() {
abstract override val sender: Member
final override val subject: Group get() = sender.group
val group: Group get() = sender.group
// final override fun toString(): String = "OnlineMessageSource.FromGroup(group=${group.id}, sender=${sender.id})"
}
}
} }
/** /**
* 序列号. 若是机器人发出去的消息, 请先 [确保 sequenceId 可用][MessageSource.ensureSequenceIdAvailable] * 引用这条消息
* @see MessageSource.id
*/ */
@ExperimentalMessageSource fun OnlineMessageSource.quote(): QuoteReply {
@get:JvmSynthetic @OptIn(MiraiInternalAPI::class)
inline val MessageSource.sequenceId: Int return QuoteReply(this)
get() = (this.id shr 32).toInt() }
/** /**
* 消息随机数. 由服务器或客户端指定后不能更改. 它是消息 id 的一部分. * 引用这条消息
* @see MessageSource.id
*/ */
@ExperimentalMessageSource fun MessageChain.quote(): QuoteReply {
@get:JvmSynthetic @OptIn(MiraiInternalAPI::class)
inline val MessageSource.messageRandom: Int return QuoteReply(this.source)
get() = this.id.toInt() }
// For MessageChain /**
* 撤回这条消息
*/
suspend inline fun OnlineMessageSource.recall() = bot.recall(this)
/** /**
* 消息 id. * 撤回这条消息
*
* 仅接收到的消息才可以获取这个 id.
*
* @see MessageSource.id
*/ */
@ExperimentalMessageSource inline fun MessageSource.recallIn(
@get:JvmSynthetic timeMillis: Long,
inline val MessageChain.id: Long coroutineContext: CoroutineContext = EmptyCoroutineContext
get() = this[MessageSource].id ): Job = bot.recallIn(this, timeMillis, coroutineContext)
// OFFLINE
/** /**
* 消息序列号, 可能来自服务器也可以发送时赋值, 不唯一. * 由一条消息中的 [QuoteReply] 得到的 [MessageSource].
* * 此消息源可能来自一条与机器人无关的消息. 因此无法提供对象化的 `sender` 或 `target` 获取.
* 仅接收到的消息才可以获取这个序列号. */
@SinceMirai("0.33.0")
abstract class OfflineMessageSource : MessageSource() {
// final override fun toString(): String = "OfflineMessageSource(sender=$senderId, target=$targetId)"
} // TODO: 2020/4/4 可能要分群和好友
// For MessageChain
/**
* 消息 id.
* 仅从服务器接收的消息才可以获取 id
* *
* @see MessageSource.id * @see MessageSource.id
*/ */
@ExperimentalMessageSource
@get:JvmSynthetic @get:JvmSynthetic
inline val MessageChain.sequenceId: Int inline val MessageChain.id: Int
get() = this.getOrNull(MessageSource)?.sequenceId ?: error("Only MessageChain from server has sequenceId") get() = this.source.id
/** /**
* 消息随机数. 由服务器或客户端指定后不能更改. 它是消息 id 的一部分. * 获取这条消息源
* @see MessageSource.id * 仅从服务器接收的消息才可以获取消息源
*/ */
@ExperimentalMessageSource
@get:JvmSynthetic @get:JvmSynthetic
inline val MessageChain.messageRandom: Int inline val MessageChain.source: MessageSource
get() = this.getOrNull(MessageSource)?.messageRandom ?: error("Only MessageChain from server has sequenceId") get() = this[MessageSource]
\ No newline at end of file
...@@ -12,66 +12,24 @@ ...@@ -12,66 +12,24 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
/** /**
* 从服务器接收的或客户端构造用来发送的群内的或好友的引用回复. * 引用回复.
* *
* 可以引用一条群消息并发送给一个好友, 或是引用好友消息发送给群. * 可以引用一条群消息并发送给一个好友, 或是引用好友消息发送给群.
* 可以引用自己发出的消息. 详见 [MessageReceipt.quote] * 可以引用自己发出的消息. 详见 [MessageReceipt.quote]
* *
* 总是使用 [quote] 来构造这个实例. * @see MessageSource 获取更多信息
*/ */
open class QuoteReply @SinceMirai("0.33.0")
@OptIn(ExperimentalMessageSource::class) class QuoteReply(val source: MessageSource) : Message, MessageMetadata {
@MiraiInternalAPI constructor(val source: MessageSource) : Message, MessageMetadata { // TODO: 2020/4/4 Metadata or Content?
companion object Key : Message.Key<QuoteReply> companion object Key : Message.Key<QuoteReply>
final override fun toString(): String = "[mirai:quote]" override fun toString(): String = "[mirai:quote]"
}
/**
* 用于发送的引用回复.
* 总是使用 [quote] 来构造实例.
*/
@OptIn(MiraiInternalAPI::class, ExperimentalMessageSource::class)
sealed class QuoteReplyToSend
@MiraiInternalAPI constructor(source: MessageSource) : QuoteReply(source) {
class ToGroup(source: MessageSource, val sender: QQ) : QuoteReplyToSend(source) {
fun createAt(): At = At(sender as Member)
}
class ToFriend(source: MessageSource) : QuoteReplyToSend(source)
}
/**
* 引用这条消息.
* @see sender 消息发送人.
*/
@ExperimentalMessageSource
@OptIn(MiraiInternalAPI::class)
fun MessageChain.quote(sender: QQ?): QuoteReplyToSend {
this.firstOrNull<MessageSource>()?.let {
return it.quote(sender)
}
error("cannot find MessageSource")
}
/**
* 引用这条消息.
* @see from 消息来源. 若是好友发送
*/
@ExperimentalMessageSource
@OptIn(MiraiInternalAPI::class)
fun MessageSource.quote(from: QQ?): QuoteReplyToSend {
return if (this.groupId != 0L) {
check(from is Member) { "sender must be Member to quote a GroupMessage" }
QuoteReplyToSend.ToGroup(this, from)
} else QuoteReplyToSend.ToFriend(this)
} }
\ No newline at end of file
...@@ -7,10 +7,7 @@ import kotlinx.coroutines.io.ByteReadChannel ...@@ -7,10 +7,7 @@ import kotlinx.coroutines.io.ByteReadChannel
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.ExperimentalMessageSource import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
...@@ -166,7 +163,6 @@ actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIA ...@@ -166,7 +163,6 @@ actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIA
* @see _lowLevelRecallFriendMessage 低级 API * @see _lowLevelRecallFriendMessage 低级 API
* @see _lowLevelRecallGroupMessage 低级 API * @see _lowLevelRecallGroupMessage 低级 API
*/ */
@ExperimentalMessageSource
@JvmSynthetic @JvmSynthetic
actual abstract suspend fun recall(source: MessageSource) actual abstract suspend fun recall(source: MessageSource)
......
...@@ -7,10 +7,14 @@ import kotlinx.coroutines.Job ...@@ -7,10 +7,14 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.JavaFriendlyAPI import net.mamoe.mirai.JavaFriendlyAPI
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.recallIn import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.data.ExperimentalMessageSource
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.message.data.QuoteReply
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.unsafeWeakRef import net.mamoe.mirai.utils.unsafeWeakRef
...@@ -31,7 +35,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef ...@@ -31,7 +35,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
actual open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSource::class) actual open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSource::class)
actual constructor( actual constructor(
actual val source: MessageSource, actual val source: OnlineMessageSource.Outgoing,
target: C, target: C,
private val botAsMember: Member? private val botAsMember: Member?
) { ) {
...@@ -51,87 +55,33 @@ actual constructor( ...@@ -51,87 +55,33 @@ actual constructor(
private val _isRecalled = atomic(false) private val _isRecalled = atomic(false)
/** @JavaFriendlyAPI
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次. @JvmName("quoteReply")
* fun __quoteReplyBlockingForJava__(message: Message): MessageReceipt<C> {
* @see Bot.recall return runBlocking { return@runBlocking quoteReply(message) }
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
*/
@OptIn(ExperimentalMessageSource::class)
actual suspend fun recall() {
@Suppress("BooleanLiteralArgument")
if (_isRecalled.compareAndSet(false, true)) {
target.bot.recall(source)
} else error("message is already or planned to be recalled")
}
/**
* 在一段时间后撤回这条消息.. [recall] 或 [recallIn] 只能被调用一次.
*
* @param millis 延迟时间, 单位为毫秒
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
*/
actual fun recallIn(millis: Long): Job {
@Suppress("BooleanLiteralArgument")
if (_isRecalled.compareAndSet(false, true)) {
return when (val contact = target) {
is QQ,
is Group -> contact.bot.recallIn(source, millis)
else -> error("Unknown contact type")
}
} else error("message is already or planned to be recalled")
}
/**
* [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable] 然后引用这条消息.
* @see MessageChain.quote 引用一条消息
*/
@OptIn(ExperimentalMessageSource::class)
actual open suspend fun quote(): QuoteReplyToSend {
this.source.ensureSequenceIdAvailable()
@OptIn(LowLevelAPI::class)
return _unsafeQuote()
}
/**
* 引用这条消息, 但不会 [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable].
* 在 sequenceId 可用前就发送这条消息则会导致一个异常.
* 当且仅当用于存储而不用于发送时使用这个方法.
*
* @see MessageChain.quote 引用一条消息
*/
@OptIn(ExperimentalMessageSource::class)
@LowLevelAPI
@Suppress("FunctionName")
actual fun _unsafeQuote(): QuoteReplyToSend {
return this.source.quote(botAsMember as? QQ)
}
/**
* 引用这条消息并回复.
* @see MessageChain.quote 引用一条消息
*/
@JvmSynthetic
actual suspend fun quoteReply(message: MessageChain) {
target.sendMessage(this.quote() + message)
} }
@JavaFriendlyAPI @JavaFriendlyAPI
@JvmName("quoteReply") @JvmName("quoteReply")
fun __quoteReplyBlockingForJava__(message: Message) { fun __quoteReplyBlockingForJava__(message: String): MessageReceipt<C> {
runBlocking { quoteReply(message) } return runBlocking { quoteReply(message) }
} }
@JavaFriendlyAPI @JavaFriendlyAPI
@JvmName("recall") @JvmName("recall")
fun __recallBlockingForJava__() { fun __recallBlockingForJava__() {
runBlocking { recall() } return runBlocking { recall() }
}
@JavaFriendlyAPI
@JvmName("recall")
fun __recallInBlockingForJava__(timeMillis: Long): Job {
return recallIn(timeMillis = timeMillis)
} }
@JavaFriendlyAPI @JavaFriendlyAPI
@JvmName("quote") @JvmName("quote")
fun __quoteBlockingForJava__() { fun __quoteBlockingForJava__(): QuoteReply {
runBlocking { quote() } return this.quote()
} }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment