mirror of
https://github.com/mamoe/mirai.git
synced 2025-04-24 20:43:33 +08:00
parent
9f7088d4a4
commit
2b49a7586f
mirai-core-qqandroid/src
androidMain/kotlin/net/mamoe/mirai/qqandroid
commonMain/kotlin/net/mamoe/mirai/qqandroid
jvmMain/kotlin/net/mamoe/mirai/qqandroid
mirai-core/src
androidMain/kotlin/net/mamoe/mirai
commonMain/kotlin/net.mamoe.mirai
jvmMain/kotlin/net/mamoe/mirai
@ -1,185 +0,0 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import kotlinx.serialization.UnstableDefault
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlinx.serialization.json.int
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.BotImpl
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
import net.mamoe.mirai.contact.*
|
||||
@ -35,8 +36,7 @@ import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.qqandroid.contact.MemberInfoImpl
|
||||
import net.mamoe.mirai.qqandroid.contact.QQImpl
|
||||
import net.mamoe.mirai.qqandroid.contact.checkIsGroupImpl
|
||||
import net.mamoe.mirai.qqandroid.message.OnlineFriendImageImpl
|
||||
import net.mamoe.mirai.qqandroid.message.OnlineGroupImageImpl
|
||||
import net.mamoe.mirai.qqandroid.message.*
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
|
||||
@ -50,11 +50,22 @@ import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString
|
||||
import net.mamoe.mirai.qqandroid.utils.toReadPacket
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.collections.asSequence
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
import kotlin.math.absoluteValue
|
||||
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)
|
||||
internal class QQAndroidBot constructor(
|
||||
context: Context,
|
||||
@ -170,69 +181,58 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
@Suppress("RemoveExplicitTypeArguments") // false positive
|
||||
@ExperimentalMessageSource
|
||||
override suspend fun recall(source: MessageSource) {
|
||||
if (source.senderId != id && source.groupId != 0L) {
|
||||
getGroup(source.groupId).checkBotPermissionOperator()
|
||||
}
|
||||
|
||||
// println(source._miraiContentToString())
|
||||
source.ensureSequenceIdAvailable()
|
||||
|
||||
network.run {
|
||||
val response: PbMessageSvc.PbMsgWithDraw.Response =
|
||||
if (source.groupId == 0L) {
|
||||
PbMessageSvc.PbMsgWithDraw.Friend(
|
||||
bot.client,
|
||||
source.senderId,
|
||||
source.sequenceId,
|
||||
source.messageRandom,
|
||||
source.time
|
||||
).sendAndExpect()
|
||||
} else {
|
||||
MessageRecallEvent.GroupRecall(
|
||||
bot,
|
||||
source.senderId,
|
||||
source.id,
|
||||
source.time.toInt(),
|
||||
null,
|
||||
getGroup(source.groupId)
|
||||
).broadcast()
|
||||
PbMessageSvc.PbMsgWithDraw.Group(
|
||||
bot.client,
|
||||
source.groupId,
|
||||
source.sequenceId,
|
||||
source.messageRandom
|
||||
).sendAndExpect()
|
||||
check(source is MessageSourceImpl)
|
||||
|
||||
val response: PbMessageSvc.PbMsgWithDraw.Response = when (source) {
|
||||
is MessageSourceToGroupImpl,
|
||||
is MessageSourceFromGroupImpl
|
||||
-> {
|
||||
val group = when (source) {
|
||||
is MessageSourceToGroupImpl -> source.target
|
||||
is MessageSourceFromGroupImpl -> source.group
|
||||
else -> error("stub")
|
||||
}
|
||||
group.checkBotPermissionOperator()
|
||||
MessageRecallEvent.GroupRecall(
|
||||
this,
|
||||
source.senderId,
|
||||
source.id,
|
||||
source.time,
|
||||
null,
|
||||
group
|
||||
).broadcast()
|
||||
|
||||
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.id}: $response" }
|
||||
network.run {
|
||||
PbMessageSvc.PbMsgWithDraw.createForGroupMessage(
|
||||
bot.asQQAndroidBot().client,
|
||||
group.id,
|
||||
source.sequenceId,
|
||||
source.id
|
||||
).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
|
||||
}
|
||||
}
|
||||
is MessageSourceFromFriendImpl,
|
||||
is MessageSourceToFriendImpl
|
||||
-> network.run {
|
||||
PbMessageSvc.PbMsgWithDraw.createForFriendMessage(
|
||||
bot.client,
|
||||
source.senderId,
|
||||
source.sequenceId,
|
||||
source.id,
|
||||
source.time
|
||||
).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
|
||||
}
|
||||
else -> error("stub")
|
||||
}
|
||||
|
||||
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.id}: $response" }
|
||||
}
|
||||
|
||||
@OptIn(LowLevelAPI::class)
|
||||
override suspend fun _lowLevelRecallFriendMessage(friendId: Long, messageId: Long, time: Long) {
|
||||
network.run {
|
||||
val response: PbMessageSvc.PbMsgWithDraw.Response =
|
||||
PbMessageSvc.PbMsgWithDraw.Friend(client, friendId, (messageId shr 32).toInt(), messageId.toInt(), time)
|
||||
.sendAndExpect()
|
||||
|
||||
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${messageId}: $response" }
|
||||
}
|
||||
}
|
||||
|
||||
@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" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@LowLevelAPI
|
||||
@MiraiExperimentalAPI
|
||||
override suspend fun _lowLevelGetAnnouncements(groupId: Long, page: Int, amount: Int): GroupAnnouncementList {
|
||||
@ -382,6 +382,7 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
val group = getGroup(groupCode)
|
||||
|
||||
val time = currentTimeSeconds
|
||||
message.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
|
||||
network.run {
|
||||
val data = message.calculateValidationDataForGroup(
|
@ -25,7 +25,9 @@ import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
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.protocol.packet.chat.TroopManagement
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
|
||||
@ -303,18 +305,17 @@ internal class GroupImpl(
|
||||
|| imageCount >= 4
|
||||
|| (event.message.any<QuoteReply>()
|
||||
&& (imageCount != 0 || length > 100))
|
||||
) {
|
||||
return bot.lowLevelSendLongGroupMessage(this.id, event.message)
|
||||
}
|
||||
) return bot.lowLevelSendLongGroupMessage(this.id, event.message)
|
||||
|
||||
msg = event.message
|
||||
} else msg = message.asMessageChain()
|
||||
msg.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
|
||||
lateinit var source: MessageSourceFromSendGroup
|
||||
lateinit var source: MessageSourceToGroupImpl
|
||||
bot.network.run {
|
||||
val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.ToGroup(
|
||||
val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.createToGroup(
|
||||
bot.client,
|
||||
id,
|
||||
this@GroupImpl,
|
||||
msg
|
||||
) {
|
||||
source = it
|
||||
|
@ -22,10 +22,13 @@ import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
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.QuoteReply
|
||||
import net.mamoe.mirai.message.data.asMessageChain
|
||||
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.packet.chat.TroopManagement
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
|
||||
@ -63,12 +66,13 @@ internal class MemberImpl constructor(
|
||||
if (event.isCancelled) {
|
||||
throw EventCancelledException("cancelled by FriendMessageSendEvent")
|
||||
}
|
||||
lateinit var source: MessageSource
|
||||
lateinit var source: MessageSourceToFriendImpl
|
||||
event.message.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
bot.network.run {
|
||||
check(
|
||||
MessageSvc.PbSendMsg.ToFriend(
|
||||
MessageSvc.PbSendMsg.createToFriend(
|
||||
bot.client,
|
||||
id,
|
||||
this@MemberImpl,
|
||||
event.message
|
||||
) {
|
||||
source = it
|
||||
|
@ -29,10 +29,13 @@ import net.mamoe.mirai.event.events.ImageUploadEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
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.QuoteReply
|
||||
import net.mamoe.mirai.message.data.asMessageChain
|
||||
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.protocol.data.proto.Cmd0x352
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
|
||||
@ -80,12 +83,13 @@ internal class QQImpl(
|
||||
if (event.isCancelled) {
|
||||
throw EventCancelledException("cancelled by FriendMessageSendEvent")
|
||||
}
|
||||
lateinit var source: MessageSource
|
||||
event.message.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
lateinit var source: MessageSourceToFriendImpl
|
||||
bot.network.run {
|
||||
check(
|
||||
MessageSvc.PbSendMsg.ToFriend(
|
||||
MessageSvc.PbSendMsg.createToFriend(
|
||||
bot.client,
|
||||
id,
|
||||
this@QQImpl,
|
||||
event.message
|
||||
) {
|
||||
source = it
|
||||
|
@ -11,104 +11,88 @@
|
||||
|
||||
package net.mamoe.mirai.qqandroid.message
|
||||
|
||||
import kotlinx.atomicfu.AtomicBoolean
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.event.subscribingGetAsync
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.message.data.messageRandom
|
||||
import net.mamoe.mirai.message.data.sequenceId
|
||||
import net.mamoe.mirai.message.data.OfflineMessageSource
|
||||
import net.mamoe.mirai.message.data.OnlineMessageSource
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
|
||||
import net.mamoe.mirai.qqandroid.utils._miraiContentToString
|
||||
import net.mamoe.mirai.qqandroid.utils.coerceAtMostOrFail
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.loadAs
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
|
||||
internal class MessageSourceFromServer(
|
||||
val delegate: ImMsgBody.SourceMsg
|
||||
) : MessageSource {
|
||||
override val time: Long get() = delegate.time.toLong() and 0xFFFFFFFF
|
||||
|
||||
override val originalMessage: MessageChain by lazy {
|
||||
delegate.toMessageChain()
|
||||
}
|
||||
internal interface MessageSourceImpl {
|
||||
val sequenceId: Int
|
||||
|
||||
override val id: Long
|
||||
get() = (delegate.origSeqs?.firstOrNull()
|
||||
?: error("cannot find sequenceId from ImMsgBody.SourceMsg")).toLong().shl(32) or
|
||||
delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.and(0xFFFFFFFF)
|
||||
|
||||
override val toUin: Long get() = delegate.toUin // always 0
|
||||
|
||||
override suspend fun ensureSequenceIdAvailable() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
// override val sourceMessage: MessageChain get() = delegate.toMessageChain()
|
||||
override val senderId: Long get() = delegate.senderUin
|
||||
override val groupId: Long get() = Group.calculateGroupCodeByGroupUin(delegate.toUin)
|
||||
|
||||
override fun toString(): String = ""
|
||||
var isRecalledOrPlanned: Boolean
|
||||
}
|
||||
|
||||
internal class MessageSourceFromMsg(
|
||||
val delegate: MsgComm.Msg
|
||||
) : MessageSource {
|
||||
override val time: Long get() = delegate.msgHead.msgTime.toLong() and 0xFFFFFFFF
|
||||
override val id: Long =
|
||||
delegate.msgHead.msgSeq.toLong().shl(32) or
|
||||
delegate.msgBody.richText.attr!!.random.toLong().and(0xFFFFFFFF)
|
||||
|
||||
override suspend fun ensureSequenceIdAvailable() {
|
||||
// nothing to do
|
||||
internal suspend inline fun MessageSource.ensureSequenceIdAvailable() {
|
||||
if (this is MessageSourceToGroupImpl) {
|
||||
this.ensureSequenceIdAvailable()
|
||||
}
|
||||
}
|
||||
|
||||
override val toUin: Long get() = delegate.msgHead.toUin
|
||||
override val senderId: Long get() = delegate.msgHead.fromUin
|
||||
override val groupId: Long get() = delegate.msgHead.groupInfo?.groupCode ?: 0
|
||||
override val originalMessage: MessageChain by lazy {
|
||||
delegate.toMessageChain()
|
||||
}
|
||||
|
||||
fun toJceData(): ImMsgBody.SourceMsg {
|
||||
return if (groupId == 0L) {
|
||||
toJceDataImplForFriend()
|
||||
} else toJceDataImplForGroup()
|
||||
}
|
||||
internal class MessageSourceFromFriendImpl(
|
||||
override val bot: Bot,
|
||||
val msg: MsgComm.Msg
|
||||
) : OnlineMessageSource.Incoming.FromFriend(), 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, isGroup = false, addSource = false) }
|
||||
override val target: Bot get() = bot
|
||||
override val sender: QQ get() = bot.getFriend(msg.msgHead.fromUin)
|
||||
|
||||
private val elems by lazy {
|
||||
delegate.msgBody.richText.elems.toMutableList().also {
|
||||
msg.msgBody.richText.elems.toMutableList().also {
|
||||
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun toJceDataImplForFriend(): ImMsgBody.SourceMsg {
|
||||
internal fun toJceDataImplForFriend(): ImMsgBody.SourceMsg {
|
||||
return ImMsgBody.SourceMsg(
|
||||
origSeqs = listOf(delegate.msgHead.msgSeq),
|
||||
senderUin = delegate.msgHead.fromUin,
|
||||
toUin = delegate.msgHead.toUin,
|
||||
origSeqs = listOf(msg.msgHead.msgSeq),
|
||||
senderUin = msg.msgHead.fromUin,
|
||||
toUin = msg.msgHead.toUin,
|
||||
flag = 1,
|
||||
elems = delegate.msgBody.richText.elems,
|
||||
elems = msg.msgBody.richText.elems,
|
||||
type = 0,
|
||||
time = delegate.msgHead.msgTime,
|
||||
time = msg.msgHead.msgTime,
|
||||
pbReserve = SourceMsg.ResvAttr(
|
||||
origUids = messageRandom.toLong() and 0xffFFffFF
|
||||
origUids = id.toULong().toLong()
|
||||
).toByteArray(SourceMsg.ResvAttr.serializer()),
|
||||
srcMsg = MsgComm.Msg(
|
||||
msgHead = MsgComm.MsgHead(
|
||||
fromUin = delegate.msgHead.fromUin, // qq
|
||||
toUin = delegate.msgHead.toUin, // group
|
||||
msgType = delegate.msgHead.msgType, // 82?
|
||||
c2cCmd = delegate.msgHead.c2cCmd,
|
||||
msgSeq = delegate.msgHead.msgSeq,
|
||||
msgTime = delegate.msgHead.msgTime,
|
||||
msgUid = messageRandom.toLong() and 0xffFFffFF, // ok
|
||||
// groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode),
|
||||
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(
|
||||
@ -119,59 +103,116 @@ internal class MessageSourceFromMsg(
|
||||
).toByteArray(MsgComm.Msg.serializer())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toJceDataImplForGroup(): ImMsgBody.SourceMsg {
|
||||
internal class MessageSourceFromGroupImpl(
|
||||
override val bot: Bot,
|
||||
private val msg: MsgComm.Msg
|
||||
) : OnlineMessageSource.Incoming.FromGroup(), MessageSourceImpl {
|
||||
private val isRecalled: AtomicBoolean = atomic(false)
|
||||
override var isRecalledOrPlanned: Boolean
|
||||
get() = isRecalled.value
|
||||
set(value) {
|
||||
isRecalled.value = value
|
||||
}
|
||||
override val sequenceId: Int get() = msg.msgHead.msgSeq
|
||||
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, isGroup = true, addSource = false) }
|
||||
override val target: Bot get() = bot
|
||||
override val sender: Member
|
||||
get() = bot.getGroup(
|
||||
msg.msgHead.groupInfo?.groupCode
|
||||
?: error("cannot find groupCode for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}")
|
||||
).getOrNull(msg.msgHead.fromUin)
|
||||
?: error("cannot find member for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}")
|
||||
|
||||
|
||||
fun toJceDataImplForGroup(): ImMsgBody.SourceMsg {
|
||||
return ImMsgBody.SourceMsg(
|
||||
origSeqs = listOf(delegate.msgHead.msgSeq),
|
||||
senderUin = delegate.msgHead.fromUin,
|
||||
origSeqs = listOf(msg.msgHead.msgSeq),
|
||||
senderUin = msg.msgHead.fromUin,
|
||||
toUin = 0,
|
||||
flag = 1,
|
||||
elems = delegate.msgBody.richText.elems,
|
||||
elems = msg.msgBody.richText.elems,
|
||||
type = 0,
|
||||
time = delegate.msgHead.msgTime,
|
||||
time = msg.msgHead.msgTime,
|
||||
pbReserve = EMPTY_BYTE_ARRAY,
|
||||
srcMsg = EMPTY_BYTE_ARRAY
|
||||
)
|
||||
}
|
||||
|
||||
override fun toString(): String = ""
|
||||
}
|
||||
|
||||
internal abstract class MessageSourceFromSend : MessageSource {
|
||||
internal class OfflineMessageSourceImpl( // from others' quotation
|
||||
val delegate: ImMsgBody.SourceMsg, override val bot: Bot
|
||||
) : OfflineMessageSource(), MessageSourceImpl {
|
||||
private val isRecalled: AtomicBoolean = atomic(false)
|
||||
override var isRecalledOrPlanned: Boolean
|
||||
get() = isRecalled.value
|
||||
set(value) {
|
||||
isRecalled.value = value
|
||||
}
|
||||
override val sequenceId: Int
|
||||
get() = delegate.origSeqs?.first() ?: error("cannot find sequenceId")
|
||||
override val time: Int get() = delegate.time
|
||||
override val originalMessage: MessageChain by lazy { delegate.toMessageChain(bot) }
|
||||
/*
|
||||
override val id: Long
|
||||
get() = (delegate.origSeqs?.firstOrNull()
|
||||
?: error("cannot find sequenceId from ImMsgBody.SourceMsg")).toLong().shl(32) or
|
||||
delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.and(0xFFFFFFFF)
|
||||
*/
|
||||
|
||||
abstract override val originalMessage: MessageChain
|
||||
override val id: Int
|
||||
get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!
|
||||
.coerceAtMostOrFail(Int.MAX_VALUE.toLong()).toInt()
|
||||
|
||||
fun toJceData(): ImMsgBody.SourceMsg {
|
||||
return if (groupId == 0L) {
|
||||
toJceDataImplForFriend()
|
||||
} else toJceDataImplForGroup()
|
||||
}
|
||||
// override val sourceMessage: MessageChain get() = delegate.toMessageChain()
|
||||
override val senderId: Long get() = delegate.senderUin
|
||||
override val targetId: Long get() = Group.calculateGroupCodeByGroupUin(delegate.toUin)
|
||||
}
|
||||
|
||||
internal class MessageSourceToFriendImpl(
|
||||
override val sequenceId: Int,
|
||||
override val id: Int,
|
||||
override val time: Int,
|
||||
override val originalMessage: MessageChain,
|
||||
override val sender: Bot,
|
||||
override val target: QQ
|
||||
) : OnlineMessageSource.Outgoing.ToFriend(), 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(groupId != 0L, true)
|
||||
originalMessage.toRichTextElems(forGroup = false, withGeneralFlags = true)
|
||||
}
|
||||
|
||||
private fun toJceDataImplForFriend(): ImMsgBody.SourceMsg {
|
||||
val messageUid: Long = 262144L.shl(32) or messageRandom.toLong().and(0xffFFffFF)
|
||||
fun toJceDataImplForFriend(): ImMsgBody.SourceMsg {
|
||||
val messageUid: Long = sequenceId.toLong().shl(32) or id.toLong().and(0xffFFffFF)
|
||||
return ImMsgBody.SourceMsg(
|
||||
origSeqs = listOf(sequenceId),
|
||||
senderUin = senderId,
|
||||
toUin = toUin,
|
||||
toUin = targetId,
|
||||
flag = 1,
|
||||
elems = elems,
|
||||
type = 0,
|
||||
time = time.toInt(),
|
||||
time = time,
|
||||
pbReserve = SourceMsg.ResvAttr(
|
||||
origUids = messageUid
|
||||
).toByteArray(SourceMsg.ResvAttr.serializer()),
|
||||
srcMsg = MsgComm.Msg(
|
||||
msgHead = MsgComm.MsgHead(
|
||||
fromUin = senderId, // qq
|
||||
toUin = toUin, // group
|
||||
toUin = targetId, // group
|
||||
msgType = 9, // 82?
|
||||
c2cCmd = 11,
|
||||
msgSeq = sequenceId,
|
||||
msgTime = time.toInt(),
|
||||
msgTime = time,
|
||||
msgUid = messageUid, // ok
|
||||
// groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode),
|
||||
isSrcMsg = true
|
||||
@ -187,28 +228,68 @@ internal abstract class MessageSourceFromSend : MessageSource {
|
||||
)
|
||||
}
|
||||
|
||||
private fun toJceDataImplForGroup(): ImMsgBody.SourceMsg {
|
||||
}
|
||||
|
||||
internal class MessageSourceToGroupImpl(
|
||||
override val id: Int,
|
||||
override val time: Int,
|
||||
override val originalMessage: MessageChain,
|
||||
override val sender: Bot,
|
||||
override val target: Group
|
||||
) : OnlineMessageSource.Outgoing.ToGroup(), 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)
|
||||
}
|
||||
private lateinit var sequenceIdDeferred: Deferred<Int>
|
||||
override val sequenceId: Int get() = sequenceIdDeferred.getCompleted()
|
||||
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
internal fun startWaitingSequenceId(coroutineScope: CoroutineScope) {
|
||||
sequenceIdDeferred =
|
||||
coroutineScope.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(
|
||||
timeoutMillis = 3000
|
||||
) {
|
||||
if (it.messageRandom == this@MessageSourceToGroupImpl.id) {
|
||||
it.sequenceId
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ensureSequenceIdAvailable() {
|
||||
sequenceIdDeferred.join()
|
||||
}
|
||||
|
||||
|
||||
fun toJceDataImplForGroup(): ImMsgBody.SourceMsg {
|
||||
return ImMsgBody.SourceMsg(
|
||||
origSeqs = listOf(sequenceId),
|
||||
senderUin = senderId,
|
||||
toUin = toUin,
|
||||
toUin = Group.calculateGroupUinByGroupCode(targetId),
|
||||
flag = 1,
|
||||
elems = elems,
|
||||
type = 0,
|
||||
time = time.toInt(),
|
||||
time = time,
|
||||
pbReserve = SourceMsg.ResvAttr(
|
||||
origUids = messageRandom.toLong() and 0xffFFffFF
|
||||
origUids = id.toLong() and 0xffFFffFF // id is actually messageRandom
|
||||
).toByteArray(SourceMsg.ResvAttr.serializer()),
|
||||
srcMsg = MsgComm.Msg(
|
||||
msgHead = MsgComm.MsgHead(
|
||||
fromUin = senderId, // qq
|
||||
toUin = toUin, // group
|
||||
toUin = Group.calculateGroupUinByGroupCode(targetId), // group
|
||||
msgType = 82, // 82?
|
||||
c2cCmd = 1,
|
||||
msgSeq = sequenceId,
|
||||
msgTime = time.toInt(),
|
||||
msgUid = messageRandom.toLong() and 0xffFFffFF, // ok
|
||||
groupInfo = MsgComm.GroupInfo(groupCode = groupId),
|
||||
msgTime = time,
|
||||
msgUid = id.toLong() and 0xffFFffFF, // ok
|
||||
groupInfo = MsgComm.GroupInfo(groupCode = targetId),
|
||||
isSrcMsg = true
|
||||
),
|
||||
msgBody = ImMsgBody.MsgBody(
|
||||
@ -221,65 +302,4 @@ internal abstract class MessageSourceFromSend : MessageSource {
|
||||
).toByteArray(MsgComm.Msg.serializer())
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
internal class MessageSourceFromSendFriend(
|
||||
val messageRandom: Int,
|
||||
override val time: Long,
|
||||
override val senderId: Long,
|
||||
override val toUin: Long,
|
||||
override val groupId: Long,
|
||||
val sequenceId: Int,
|
||||
override val originalMessage: MessageChain
|
||||
) : MessageSourceFromSend() {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override val id: Long
|
||||
get() = sequenceId.toLong().shl(32) or
|
||||
messageRandom.toLong().and(0xFFFFFFFF)
|
||||
|
||||
override suspend fun ensureSequenceIdAvailable() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
internal class MessageSourceFromSendGroup(
|
||||
val messageRandom: Int,
|
||||
override val time: Long,
|
||||
override val senderId: Long,
|
||||
override val toUin: Long,
|
||||
override val groupId: Long,
|
||||
override val originalMessage: MessageChain
|
||||
) : MessageSourceFromSend() {
|
||||
private lateinit var sequenceIdDeferred: Deferred<Int>
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override val id: Long
|
||||
get() = sequenceIdDeferred.getCompleted().toLong().shl(32) or
|
||||
messageRandom.toLong().and(0xFFFFFFFF)
|
||||
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
internal fun startWaitingSequenceId(coroutineScope: CoroutineScope) {
|
||||
sequenceIdDeferred =
|
||||
coroutineScope.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(
|
||||
timeoutMillis = 3000
|
||||
) {
|
||||
if (it.messageRandom == this@MessageSourceFromSendGroup.messageRandom) {
|
||||
it.sequenceId
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun ensureSequenceIdAvailable() {
|
||||
sequenceIdDeferred.join()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return ""
|
||||
}
|
||||
}
|
@ -14,8 +14,8 @@ package net.mamoe.mirai.qqandroid.message
|
||||
import kotlinx.io.core.discardExact
|
||||
import kotlinx.io.core.readUInt
|
||||
import kotlinx.io.core.toByteArray
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
import net.mamoe.mirai.contact.Member
|
||||
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.ImMsgBody
|
||||
@ -42,9 +42,11 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
|
||||
|
||||
if (this.any<QuoteReply>()) {
|
||||
when (val source = this[QuoteReply].source) {
|
||||
is MessageSourceFromServer -> elements.add(ImMsgBody.Elem(srcMsg = source.delegate))
|
||||
is MessageSourceFromMsg -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData()))
|
||||
is MessageSourceFromSend -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData()))
|
||||
is OfflineMessageSourceImpl -> 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 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}")
|
||||
}
|
||||
}
|
||||
@ -122,19 +124,17 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
|
||||
.also { transformOneMessage(UNSUPPORTED_FLASH_MESSAGE_PLAIN) }
|
||||
is AtAll -> elements.add(atAllData)
|
||||
is Face -> elements.add(ImMsgBody.Elem(face = it.toJceData()))
|
||||
is QuoteReplyToSend -> {
|
||||
is QuoteReply -> {
|
||||
if (forGroup) {
|
||||
check(it is QuoteReplyToSend.ToGroup) {
|
||||
"sending a quote to group using QuoteReplyToSend.ToFriend is prohibited"
|
||||
when (val source = it.source) {
|
||||
is OnlineMessageSource.Incoming.FromGroup -> {
|
||||
transformOneMessage(At(source.sender))
|
||||
transformOneMessage(PlainText(" "))
|
||||
}
|
||||
}
|
||||
if (it.sender is Member) {
|
||||
transformOneMessage(it.createAt())
|
||||
}
|
||||
transformOneMessage(PlainText(" "))
|
||||
}
|
||||
}
|
||||
is QuoteReply, // already transformed above
|
||||
is MessageSource, // mirai only
|
||||
is MessageSource, // mirai metadata only
|
||||
is RichMessage // already transformed above
|
||||
-> {
|
||||
|
||||
@ -173,28 +173,36 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
|
||||
|
||||
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()
|
||||
|
||||
@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_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes()
|
||||
|
||||
@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
|
||||
|
||||
return buildMessageChain(elements.size + 1) {
|
||||
+MessageSourceFromMsg(delegate = this@toMessageChain)
|
||||
elements.joinToMessageChain(this)
|
||||
if (addSource) {
|
||||
if (isGroup) {
|
||||
+MessageSourceFromGroupImpl(bot, this@toMessageChain)
|
||||
} else {
|
||||
+MessageSourceFromFriendImpl(bot, this@toMessageChain)
|
||||
}
|
||||
}
|
||||
elements.joinToMessageChain(bot, this)
|
||||
}.cleanupRubbishMessageElements()
|
||||
}
|
||||
|
||||
// These two functions have difference method signature, don't combine.
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
||||
internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain {
|
||||
internal fun ImMsgBody.SourceMsg.toMessageChain(bot: Bot): MessageChain {
|
||||
val elements = this.elems!!
|
||||
|
||||
return buildMessageChain(elements.size + 1) {
|
||||
+MessageSourceFromServer(delegate = this@toMessageChain)
|
||||
elements.joinToMessageChain(this)
|
||||
+OfflineMessageSourceImpl(delegate = this@toMessageChain, bot = bot)
|
||||
elements.joinToMessageChain(bot, this)
|
||||
}.cleanupRubbishMessageElements()
|
||||
}
|
||||
|
||||
@ -228,7 +236,7 @@ private fun MessageChain.cleanupRubbishMessageElements(): MessageChain {
|
||||
}
|
||||
|
||||
internal inline fun <reified R> Iterable<*>.firstIsInstance(): R {
|
||||
this.forEach {
|
||||
for (it in this) {
|
||||
if (it is R) {
|
||||
return it
|
||||
}
|
||||
@ -236,12 +244,21 @@ internal inline fun <reified R> Iterable<*>.firstIsInstance(): R {
|
||||
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)
|
||||
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilder) {
|
||||
internal fun List<ImMsgBody.Elem>.joinToMessageChain(bot: Bot, message: MessageChainBuilder) {
|
||||
// (this._miraiContentToString())
|
||||
this.forEach {
|
||||
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.customFace != null -> message.add(OnlineGroupImageImpl(it.customFace))
|
||||
it.face != null -> message.add(Face(it.face.index))
|
||||
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
@ -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
|
||||
fun Group(
|
||||
fun createForGroupMessage(
|
||||
client: QQAndroidClient,
|
||||
groupCode: Long,
|
||||
messageSequenceId: Int, // 56639
|
||||
@ -71,12 +73,12 @@ internal class PbMessageSvc {
|
||||
)
|
||||
}
|
||||
|
||||
fun Friend(
|
||||
fun createForFriendMessage(
|
||||
client: QQAndroidClient,
|
||||
toUin: Long,
|
||||
messageSequenceId: Int, // 56639
|
||||
messageRandom: Int, // 921878719
|
||||
time: Long
|
||||
time: Int
|
||||
): OutgoingPacket = buildOutgoingUniPacket(client) {
|
||||
val messageUid: Long = 262144L.shl(32) or messageRandom.toLong().and(0xffFFffFF)
|
||||
writeProtoBuf(
|
||||
@ -91,7 +93,7 @@ internal class PbMessageSvc {
|
||||
toUin = toUin,
|
||||
msgSeq = messageSequenceId,
|
||||
msgUid = messageUid,
|
||||
msgTime = time and 0xffffffff,
|
||||
msgTime = time.toULong().toLong(),
|
||||
routingHead = MsgSvc.RoutingHead(
|
||||
c2c = MsgSvc.C2C(
|
||||
toUin = toUin
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
@file: OptIn(LowLevelAPI::class)
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
|
||||
|
||||
@ -19,6 +20,7 @@ import kotlinx.io.core.discardExact
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.data.MemberInfo
|
||||
import net.mamoe.mirai.event.events.BotJoinGroupEvent
|
||||
import net.mamoe.mirai.event.events.BotOfflineEvent
|
||||
@ -29,8 +31,8 @@ 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.checkIsQQImpl
|
||||
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendFriend
|
||||
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup
|
||||
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.network.MultiPacketByIterable
|
||||
@ -82,6 +84,7 @@ internal class MessageSvc {
|
||||
*/
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
internal object PbGetMsg : OutgoingPacketFactory<PbGetMsg.Response>("MessageSvc.PbGetMsg") {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient,
|
||||
syncFlag: MsgSvc.SyncFlag = MsgSvc.SyncFlag.START,
|
||||
@ -226,7 +229,7 @@ internal class MessageSvc {
|
||||
if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
|
||||
return@mapNotNull FriendMessage(
|
||||
friend,
|
||||
msg.toMessageChain()
|
||||
msg.toMessageChain(bot, isGroup = false, addSource = true)
|
||||
)
|
||||
}
|
||||
} else return@mapNotNull null
|
||||
@ -289,34 +292,33 @@ internal class MessageSvc {
|
||||
}
|
||||
}
|
||||
|
||||
inline fun ToFriend(
|
||||
inline fun createToFriend(
|
||||
client: QQAndroidClient,
|
||||
toUin: Long,
|
||||
qq: QQ,
|
||||
message: MessageChain,
|
||||
crossinline sourceCallback: (MessageSourceFromSendFriend) -> Unit
|
||||
crossinline sourceCallback: (MessageSourceToFriendImpl) -> Unit
|
||||
): OutgoingPacket {
|
||||
val source = MessageSourceFromSendFriend(
|
||||
messageRandom = Random.nextInt().absoluteValue,
|
||||
senderId = client.uin,
|
||||
toUin = toUin,
|
||||
time = currentTimeSeconds,
|
||||
groupId = 0,
|
||||
val source = MessageSourceToFriendImpl(
|
||||
id = Random.nextInt().absoluteValue,
|
||||
sender = client.bot,
|
||||
target = qq,
|
||||
time = currentTimeSeconds.toInt(),
|
||||
sequenceId = client.atomicNextMessageSequenceId(),
|
||||
originalMessage = message
|
||||
)
|
||||
sourceCallback(source)
|
||||
return ToFriend(client, toUin, message, source)
|
||||
return createToFriend(client, qq.id, message, source)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送好友消息
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
private fun ToFriend(
|
||||
private fun createToFriend(
|
||||
client: QQAndroidClient,
|
||||
toUin: Long,
|
||||
message: MessageChain,
|
||||
source: MessageSourceFromSendFriend
|
||||
source: MessageSourceToFriendImpl
|
||||
): 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())
|
||||
|
||||
@ -331,43 +333,42 @@ internal class MessageSvc {
|
||||
)
|
||||
),
|
||||
msgSeq = source.sequenceId,
|
||||
msgRand = source.messageRandom,
|
||||
syncCookie = SyncCookie(time = source.time).toByteArray(SyncCookie.serializer())
|
||||
msgRand = source.id,
|
||||
syncCookie = SyncCookie(time = source.time.toULong().toLong()).toByteArray(SyncCookie.serializer())
|
||||
// msgVia = 1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
inline fun ToGroup(
|
||||
inline fun createToGroup(
|
||||
client: QQAndroidClient,
|
||||
groupCode: Long,
|
||||
group: Group,
|
||||
message: MessageChain,
|
||||
sourceCallback: (MessageSourceFromSendGroup) -> Unit
|
||||
sourceCallback: (MessageSourceToGroupImpl) -> Unit
|
||||
): OutgoingPacket {
|
||||
|
||||
val source = MessageSourceFromSendGroup(
|
||||
messageRandom = Random.nextInt().absoluteValue,
|
||||
senderId = client.uin,
|
||||
toUin = Group.calculateGroupUinByGroupCode(groupCode),
|
||||
time = currentTimeSeconds,
|
||||
groupId = groupCode,
|
||||
val source = MessageSourceToGroupImpl(
|
||||
id = Random.nextInt().absoluteValue,
|
||||
sender = client.bot,
|
||||
target = group,
|
||||
time = currentTimeSeconds.toInt(),
|
||||
originalMessage = message//,
|
||||
// sourceMessage = message
|
||||
)
|
||||
sourceCallback(source)
|
||||
return ToGroup(client, groupCode, message, source)
|
||||
return createToGroup(client, group.id, message, source)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送群消息
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
private fun ToGroup(
|
||||
private fun createToGroup(
|
||||
client: QQAndroidClient,
|
||||
groupCode: Long,
|
||||
message: MessageChain,
|
||||
source: MessageSourceFromSendGroup
|
||||
source: MessageSourceToGroupImpl
|
||||
): 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())
|
||||
|
||||
@ -384,7 +385,7 @@ internal class MessageSvc {
|
||||
)
|
||||
),
|
||||
msgSeq = client.atomicNextMessageSequenceId(),
|
||||
msgRand = source.messageRandom,
|
||||
msgRand = source.id,
|
||||
syncCookie = EMPTY_BYTE_ARRAY,
|
||||
msgVia = 1
|
||||
)
|
||||
|
@ -84,7 +84,7 @@ internal class OnlinePush {
|
||||
return GroupMessage(
|
||||
senderName = pbPushMsg.msg.msgHead.groupInfo.groupCard,
|
||||
sender = group[pbPushMsg.msg.msgHead.fromUin],
|
||||
message = pbPushMsg.msg.toMessageChain(),
|
||||
message = pbPushMsg.msg.toMessageChain(bot, isGroup = true, addSource = true),
|
||||
permission = when {
|
||||
flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
|
||||
flags and 8 != 0 -> MemberPermission.OWNER
|
||||
@ -369,13 +369,12 @@ internal class OnlinePush {
|
||||
if (meta.authorUin == bot.id) {
|
||||
null
|
||||
} else MessageRecallEvent.GroupRecall(
|
||||
bot,
|
||||
meta.authorUin,
|
||||
meta.seq.toLong().shl(32) or
|
||||
meta.msgRandom.toLong().and(0xffffffff),
|
||||
meta.time,
|
||||
memebr,
|
||||
group
|
||||
bot = bot,
|
||||
authorId = meta.authorUin,
|
||||
messageId = meta.msgRandom,
|
||||
messageTime = meta.time,
|
||||
operator = memebr,
|
||||
group = group
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.KType
|
||||
|
||||
|
||||
private val indent: String = " ".repeat(4)
|
||||
|
||||
/**
|
||||
@ -126,10 +127,12 @@ internal fun Any?._miraiContentToString(prefix: String = ""): String = when (thi
|
||||
internal expect fun KProperty1<*, *>.getValueAgainstPermission(receiver: Any): Any?
|
||||
|
||||
@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")
|
||||
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(
|
||||
prefix: String,
|
||||
|
@ -7,7 +7,6 @@ import kotlinx.coroutines.io.ByteReadChannel
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.data.AddFriendResult
|
||||
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.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
@ -156,7 +155,6 @@ actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIA
|
||||
* @see _lowLevelRecallFriendMessage 低级 API
|
||||
* @see _lowLevelRecallGroupMessage 低级 API
|
||||
*/
|
||||
@ExperimentalMessageSource
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun recall(source: MessageSource)
|
||||
|
||||
|
@ -7,10 +7,14 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.JavaFriendlyAPI
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.recallIn
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
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.getValue
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
@ -31,7 +35,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
actual open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSource::class)
|
||||
actual constructor(
|
||||
actual val source: MessageSource,
|
||||
actual val source: OnlineMessageSource.Outgoing,
|
||||
target: C,
|
||||
private val botAsMember: Member?
|
||||
) {
|
||||
@ -51,96 +55,33 @@ actual constructor(
|
||||
|
||||
private val _isRecalled = atomic(false)
|
||||
|
||||
/**
|
||||
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
|
||||
*
|
||||
* @see Bot.recall
|
||||
* @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")
|
||||
@JavaFriendlyAPI
|
||||
@JvmName("quoteReply")
|
||||
fun __quoteReplyBlockingForJava__(message: Message): MessageReceipt<C> {
|
||||
return runBlocking { return@runBlocking quoteReply(message) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一段时间后撤回这条消息.. [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
|
||||
@JvmName("quoteReply")
|
||||
fun __quoteReplyBlockingForJava__(message: Message) {
|
||||
runBlocking { quoteReply(message) }
|
||||
fun __quoteReplyBlockingForJava__(message: String): MessageReceipt<C> {
|
||||
return runBlocking { quoteReply(message) }
|
||||
}
|
||||
|
||||
@JavaFriendlyAPI
|
||||
@JvmName("recall")
|
||||
fun __recallBlockingForJava__() {
|
||||
runBlocking { recall() }
|
||||
return runBlocking { return@runBlocking recall() }
|
||||
}
|
||||
|
||||
@JavaFriendlyAPI
|
||||
@JvmName("recall")
|
||||
fun __recallInBlockingForJava__(timeMillis: Long): Job {
|
||||
return recallIn(timeMillis = timeMillis)
|
||||
}
|
||||
|
||||
@JavaFriendlyAPI
|
||||
@JvmName("quote")
|
||||
fun __quoteBlockingForJava__() {
|
||||
runBlocking { quote() }
|
||||
fun __quoteBlockingForJava__(): QuoteReply {
|
||||
return this.quote()
|
||||
}
|
||||
}
|
@ -19,10 +19,7 @@ import kotlinx.coroutines.launch
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.data.AddFriendResult
|
||||
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.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.utils.*
|
||||
@ -170,10 +167,8 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
|
||||
* @throws PermissionDeniedException 当 [Bot] 无权限操作时
|
||||
*
|
||||
* @see Bot.recall (扩展函数) 接受参数 [MessageChain]
|
||||
* @see _lowLevelRecallFriendMessage 低级 API
|
||||
* @see _lowLevelRecallGroupMessage 低级 API
|
||||
* @see MessageSource.recall
|
||||
*/
|
||||
@ExperimentalMessageSource
|
||||
@JvmSynthetic
|
||||
abstract suspend fun recall(source: MessageSource)
|
||||
|
||||
@ -222,20 +217,19 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
|
||||
|
||||
/**
|
||||
* 撤回这条消息.
|
||||
* 根据 [message] 内的 [MessageSource] 进行相关判断.
|
||||
*
|
||||
* [Bot] 撤回自己的消息不需要权限.
|
||||
* [Bot] 撤回群员的消息需要管理员权限.
|
||||
* [Bot] 撤回自己的消息不需要权限, 但需要在发出后 2 分钟内撤回.
|
||||
* [Bot] 撤回群员的消息需要管理员权限, 可在任意时间撤回.
|
||||
*
|
||||
* @throws PermissionDeniedException 当 [Bot] 无权限操作时
|
||||
* @see Bot.recall
|
||||
*/
|
||||
@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 coroutineContext 额外的 [CoroutineContext]
|
||||
|
@ -109,8 +109,7 @@ sealed class MessageRecallEvent : BotEvent {
|
||||
* 消息 id.
|
||||
* @see MessageSource.id
|
||||
*/
|
||||
@ExperimentalMessageSource
|
||||
abstract val messageId: Long
|
||||
abstract val messageId: Int
|
||||
|
||||
/**
|
||||
* 原发送时间
|
||||
@ -122,8 +121,7 @@ sealed class MessageRecallEvent : BotEvent {
|
||||
*/
|
||||
data class FriendRecall(
|
||||
override val bot: Bot,
|
||||
@ExperimentalMessageSource
|
||||
override val messageId: Long,
|
||||
override val messageId: Int,
|
||||
override val messageTime: Int,
|
||||
/**
|
||||
* 撤回操作人, 可能为 [Bot.uin] 或好友的 [QQ.id]
|
||||
@ -137,8 +135,7 @@ sealed class MessageRecallEvent : BotEvent {
|
||||
data class GroupRecall(
|
||||
override val bot: Bot,
|
||||
override val authorId: Long,
|
||||
@ExperimentalMessageSource
|
||||
override val messageId: Long,
|
||||
override val messageId: Int,
|
||||
override val messageTime: Int,
|
||||
/**
|
||||
* 操作人. 为 null 时则为 [Bot] 操作.
|
||||
|
@ -13,7 +13,6 @@ import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.data.*
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import net.mamoe.mirai.utils.WeakRef
|
||||
@ -69,21 +68,6 @@ interface LowLevelBotAPIAccessor {
|
||||
@LowLevelAPI
|
||||
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 页码
|
||||
@ -131,20 +115,3 @@ interface LowLevelBotAPIAccessor {
|
||||
@MiraiExperimentalAPI
|
||||
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)
|
||||
)
|
||||
}
|
@ -13,6 +13,8 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
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
|
||||
|
||||
@ -23,6 +25,7 @@ class FriendMessage(
|
||||
override val sender: QQ by sender.unsafeWeakRef()
|
||||
override val bot: Bot get() = sender.bot
|
||||
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)"
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.event.Event
|
||||
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
|
||||
|
||||
@ -34,6 +36,8 @@ class GroupMessage(
|
||||
|
||||
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]
|
||||
|
||||
override fun toString(): String =
|
||||
|
@ -177,8 +177,7 @@ abstract class MessagePacketBase<out TSender : QQ, out TSubject : Contact> : Pac
|
||||
@JvmName("reply2")
|
||||
suspend inline fun MessageChain.quoteReply(): MessageReceipt<TSubject> = quoteReply(this)
|
||||
|
||||
@ExperimentalMessageSource
|
||||
inline fun MessageChain.quote(): QuoteReplyToSend = this.quote(sender)
|
||||
open val source: OnlineMessageSource.Incoming get() = message.source as OnlineMessageSource.Incoming
|
||||
|
||||
// endregion
|
||||
|
||||
|
@ -7,14 +7,17 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.message
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.recallIn
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
/**
|
||||
@ -29,19 +32,17 @@ import kotlin.jvm.JvmSynthetic
|
||||
* @see QQ.sendMessage 发送群消息, 返回回执(此对象)
|
||||
*
|
||||
* @see MessageReceipt.sourceId 源 id
|
||||
* @see MessageReceipt.sourceSequenceId 源序列号
|
||||
* @see MessageReceipt.sourceTime 源时间
|
||||
*/
|
||||
expect open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSource::class) constructor(
|
||||
source: MessageSource,
|
||||
source: OnlineMessageSource.Outgoing,
|
||||
target: C,
|
||||
botAsMember: Member?
|
||||
) {
|
||||
/**
|
||||
* 指代发送出去的消息
|
||||
*/
|
||||
@ExperimentalMessageSource
|
||||
val source: MessageSource
|
||||
val source: OnlineMessageSource.Outgoing
|
||||
|
||||
/**
|
||||
* 发送目标, 为 [Group] 或 [QQ]
|
||||
@ -52,82 +53,72 @@ expect open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSour
|
||||
* 是否为发送给群的消息的回执
|
||||
*/
|
||||
val isToGroup: Boolean
|
||||
|
||||
/**
|
||||
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
|
||||
*
|
||||
* @see Bot.recall
|
||||
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
|
||||
*/
|
||||
suspend fun recall()
|
||||
|
||||
/**
|
||||
* 在一段时间后撤回这条消息.. [recall] 或 [recallIn] 只能被调用一次.
|
||||
*
|
||||
* @param millis 延迟时间, 单位为毫秒
|
||||
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
|
||||
*/
|
||||
fun recallIn(millis: Long): Job
|
||||
|
||||
/**
|
||||
* [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable] 然后引用这条消息.
|
||||
* @see MessageChain.quote 引用一条消息
|
||||
*/
|
||||
open suspend fun quote(): QuoteReplyToSend
|
||||
|
||||
/**
|
||||
* 引用这条消息, 但不会 [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable].
|
||||
* 在 sequenceId 可用前就发送这条消息则会导致一个异常.
|
||||
* 当且仅当用于存储而不用于发送时使用这个方法.
|
||||
*
|
||||
* @see MessageChain.quote 引用一条消息
|
||||
*/
|
||||
@LowLevelAPI
|
||||
@Suppress("FunctionName")
|
||||
fun _unsafeQuote(): QuoteReplyToSend
|
||||
|
||||
/**
|
||||
* 引用这条消息并回复.
|
||||
* @see MessageChain.quote 引用一条消息
|
||||
*/
|
||||
suspend fun quoteReply(message: MessageChain)
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
|
||||
*
|
||||
* @see Bot.recall
|
||||
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
|
||||
*/
|
||||
suspend inline fun MessageReceipt<*>.recall() {
|
||||
return target.bot.recall(source)
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一段时间后撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
|
||||
*
|
||||
* @param timeMillis 延迟时间, 单位为毫秒
|
||||
* @throws IllegalStateException 当此消息已经被撤回或正计划撤回时
|
||||
*/
|
||||
inline fun MessageReceipt<*>.recallIn(
|
||||
timeMillis: Long,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||
): Job = source.recallIn(timeMillis, coroutineContext)
|
||||
|
||||
|
||||
/**
|
||||
* 引用这条消息.
|
||||
* @see MessageChain.quote 引用一条消息
|
||||
*/
|
||||
@JvmSynthetic
|
||||
inline fun MessageReceipt<*>.quote(): QuoteReply = this.source.quote()
|
||||
|
||||
/**
|
||||
* 引用这条消息并回复.
|
||||
* @see MessageChain.quote 引用一条消息
|
||||
*/
|
||||
@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>
|
||||
}
|
||||
|
||||
/**
|
||||
* 引用这条消息并回复.
|
||||
* @see MessageChain.quote 引用一条消息
|
||||
*/
|
||||
@JvmSynthetic
|
||||
suspend inline fun <C : Contact> MessageReceipt<C>.quoteReply(message: String): MessageReceipt<C> {
|
||||
return this.quoteReply(message.toMessage())
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取源消息 [MessageSource.id]
|
||||
*
|
||||
* @see MessageSource.id
|
||||
*/
|
||||
@get:JvmSynthetic
|
||||
@ExperimentalMessageSource
|
||||
inline val MessageReceipt<*>.sourceId: Long
|
||||
inline val MessageReceipt<*>.sourceId: Int
|
||||
get() = this.source.id
|
||||
|
||||
/**
|
||||
* 获取源消息 [MessageSource.sequenceId]
|
||||
*
|
||||
* @see MessageSource.sequenceId
|
||||
*/
|
||||
@get:JvmSynthetic
|
||||
@ExperimentalMessageSource
|
||||
inline val MessageReceipt<*>.sourceSequenceId: Int
|
||||
get() = this.source.sequenceId
|
||||
|
||||
/**
|
||||
* 获取源消息 [MessageSource.time]
|
||||
*
|
||||
* @see MessageSource.time
|
||||
*/
|
||||
@get:JvmSynthetic
|
||||
@ExperimentalMessageSource
|
||||
inline val MessageReceipt<*>.sourceTime: Long
|
||||
inline val MessageReceipt<*>.sourceTime: Int
|
||||
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 @@
|
||||
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("MessageUtils")
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
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.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
@ -33,108 +43,189 @@ annotation class ExperimentalMessageSource
|
||||
*
|
||||
* @see Bot.recall 撤回一条消息
|
||||
* @see MessageSource.quote 引用这条消息, 创建 [MessageChain]
|
||||
*
|
||||
* @see OnlineMessageSource 在线消息的 [MessageSource]
|
||||
* @see OfflineMessageSource 离线消息的 [MessageSource]
|
||||
*/
|
||||
@ExperimentalMessageSource
|
||||
interface MessageSource : Message, MessageMetadata {
|
||||
@SinceMirai("0.33.0")
|
||||
sealed class MessageSource : Message, MessageMetadata {
|
||||
companion object Key : Message.Key<MessageSource>
|
||||
|
||||
/**
|
||||
* 在 Mirai 中使用的 id.
|
||||
* 高 32 位为 [sequenceId],
|
||||
* 低 32 位为 [messageRandom]
|
||||
*
|
||||
* @see ensureSequenceIdAvailable 确保 sequenceId 可用
|
||||
* 所属 [Bot]
|
||||
*/
|
||||
val id: Long
|
||||
abstract val bot: Bot
|
||||
|
||||
/**
|
||||
* 等待 [sequenceId] 获取, 确保其可用.
|
||||
*
|
||||
* 这个方法 3 秒超时, 抛出 [IllegalStateException], 则表明原消息发送失败.
|
||||
* 消息 id.
|
||||
*/
|
||||
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
|
||||
val originalMessage: MessageChain
|
||||
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]
|
||||
*/
|
||||
abstract val sender: Any
|
||||
|
||||
/**
|
||||
* 固定返回空字符串 ("")
|
||||
* 消息发送目标. 可能为 [机器人][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
|
||||
@get:JvmSynthetic
|
||||
inline val MessageSource.sequenceId: Int
|
||||
get() = (this.id shr 32).toInt()
|
||||
fun OnlineMessageSource.quote(): QuoteReply {
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
return QuoteReply(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息随机数. 由服务器或客户端指定后不能更改. 它是消息 id 的一部分.
|
||||
* @see MessageSource.id
|
||||
* 引用这条消息
|
||||
*/
|
||||
@ExperimentalMessageSource
|
||||
@get:JvmSynthetic
|
||||
inline val MessageSource.messageRandom: Int
|
||||
get() = this.id.toInt()
|
||||
fun MessageChain.quote(): QuoteReply {
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
return QuoteReply(this.source)
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回这条消息
|
||||
*/
|
||||
suspend inline fun OnlineMessageSource.recall() = bot.recall(this)
|
||||
|
||||
/**
|
||||
* 撤回这条消息
|
||||
*/
|
||||
inline fun MessageSource.recallIn(
|
||||
timeMillis: Long,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||
): 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.
|
||||
* 仅从服务器接收的消息才可以获取 id
|
||||
*
|
||||
* @see MessageSource.id
|
||||
*/
|
||||
@ExperimentalMessageSource
|
||||
@get:JvmSynthetic
|
||||
inline val MessageChain.id: Long
|
||||
get() = this[MessageSource].id
|
||||
inline val MessageChain.id: Int
|
||||
get() = this.source.id
|
||||
|
||||
/**
|
||||
* 消息序列号, 可能来自服务器也可以发送时赋值, 不唯一.
|
||||
*
|
||||
* 仅接收到的消息才可以获取这个序列号.
|
||||
*
|
||||
* @see MessageSource.id
|
||||
* 获取这条消息源
|
||||
* 仅从服务器接收的消息才可以获取消息源
|
||||
*/
|
||||
@ExperimentalMessageSource
|
||||
@get:JvmSynthetic
|
||||
inline val MessageChain.sequenceId: Int
|
||||
get() = this.getOrNull(MessageSource)?.sequenceId ?: error("Only MessageChain from server has sequenceId")
|
||||
|
||||
/**
|
||||
* 消息随机数. 由服务器或客户端指定后不能更改. 它是消息 id 的一部分.
|
||||
* @see MessageSource.id
|
||||
*/
|
||||
@ExperimentalMessageSource
|
||||
@get:JvmSynthetic
|
||||
inline val MessageChain.messageRandom: Int
|
||||
get() = this.getOrNull(MessageSource)?.messageRandom ?: error("Only MessageChain from server has sequenceId")
|
||||
inline val MessageChain.source: MessageSource
|
||||
get() = this[MessageSource]
|
@ -12,66 +12,24 @@
|
||||
|
||||
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.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
|
||||
/**
|
||||
* 从服务器接收的或客户端构造用来发送的群内的或好友的引用回复.
|
||||
* 引用回复.
|
||||
*
|
||||
* 可以引用一条群消息并发送给一个好友, 或是引用好友消息发送给群.
|
||||
* 可以引用自己发出的消息. 详见 [MessageReceipt.quote]
|
||||
*
|
||||
* 总是使用 [quote] 来构造这个实例.
|
||||
* @see MessageSource 获取更多信息
|
||||
*/
|
||||
open class QuoteReply
|
||||
@OptIn(ExperimentalMessageSource::class)
|
||||
@MiraiInternalAPI constructor(val source: MessageSource) : Message, MessageMetadata {
|
||||
@SinceMirai("0.33.0")
|
||||
class QuoteReply(val source: MessageSource) : Message, MessageMetadata {
|
||||
// TODO: 2020/4/4 Metadata or Content?
|
||||
companion object Key : Message.Key<QuoteReply>
|
||||
|
||||
final 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)
|
||||
override fun toString(): String = "[mirai:quote]"
|
||||
}
|
@ -7,10 +7,7 @@ import kotlinx.coroutines.io.ByteReadChannel
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.data.AddFriendResult
|
||||
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.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.utils.*
|
||||
@ -166,7 +163,6 @@ actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIA
|
||||
* @see _lowLevelRecallFriendMessage 低级 API
|
||||
* @see _lowLevelRecallGroupMessage 低级 API
|
||||
*/
|
||||
@ExperimentalMessageSource
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun recall(source: MessageSource)
|
||||
|
||||
|
@ -7,10 +7,14 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.JavaFriendlyAPI
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.recallIn
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
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.getValue
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
@ -31,7 +35,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
actual open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSource::class)
|
||||
actual constructor(
|
||||
actual val source: MessageSource,
|
||||
actual val source: OnlineMessageSource.Outgoing,
|
||||
target: C,
|
||||
private val botAsMember: Member?
|
||||
) {
|
||||
@ -51,87 +55,33 @@ actual constructor(
|
||||
|
||||
private val _isRecalled = atomic(false)
|
||||
|
||||
/**
|
||||
* 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次.
|
||||
*
|
||||
* @see Bot.recall
|
||||
* @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")
|
||||
@JavaFriendlyAPI
|
||||
@JvmName("quoteReply")
|
||||
fun __quoteReplyBlockingForJava__(message: Message): MessageReceipt<C> {
|
||||
return runBlocking { return@runBlocking quoteReply(message) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一段时间后撤回这条消息.. [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
|
||||
@JvmName("quoteReply")
|
||||
fun __quoteReplyBlockingForJava__(message: Message) {
|
||||
runBlocking { quoteReply(message) }
|
||||
fun __quoteReplyBlockingForJava__(message: String): MessageReceipt<C> {
|
||||
return runBlocking { quoteReply(message) }
|
||||
}
|
||||
|
||||
@JavaFriendlyAPI
|
||||
@JvmName("recall")
|
||||
fun __recallBlockingForJava__() {
|
||||
runBlocking { recall() }
|
||||
return runBlocking { recall() }
|
||||
}
|
||||
|
||||
@JavaFriendlyAPI
|
||||
@JvmName("recall")
|
||||
fun __recallInBlockingForJava__(timeMillis: Long): Job {
|
||||
return recallIn(timeMillis = timeMillis)
|
||||
}
|
||||
|
||||
@JavaFriendlyAPI
|
||||
@JvmName("quote")
|
||||
fun __quoteBlockingForJava__() {
|
||||
runBlocking { quote() }
|
||||
fun __quoteBlockingForJava__(): QuoteReply {
|
||||
return this.quote()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user