mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-24 14:30:09 +08:00
Add MessageSource.internalId
, support OfflineMessageSource
building
This commit is contained in:
parent
6311c39c97
commit
66f4620292
@ -35,6 +35,7 @@ import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.MemberJoinRequestEvent
|
||||
import net.mamoe.mirai.event.events.MessageRecallEvent
|
||||
import net.mamoe.mirai.event.events.NewFriendRequestEvent
|
||||
import net.mamoe.mirai.event.internal.MiraiAtomicBoolean
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
@ -45,6 +46,7 @@ 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
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.LongMsg
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.*
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
|
||||
@ -321,7 +323,7 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
bot.asQQAndroidBot().client,
|
||||
group.id,
|
||||
source.sequenceId,
|
||||
source.random
|
||||
source.internalId
|
||||
).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
|
||||
}
|
||||
}
|
||||
@ -335,7 +337,7 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
bot.client,
|
||||
source.targetId,
|
||||
source.sequenceId,
|
||||
source.random,
|
||||
source.internalId,
|
||||
source.time
|
||||
).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
|
||||
}
|
||||
@ -351,7 +353,7 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
source.target.group.id,
|
||||
source.targetId,
|
||||
source.sequenceId,
|
||||
source.random,
|
||||
source.internalId,
|
||||
source.time
|
||||
).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
|
||||
}
|
||||
@ -365,7 +367,7 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
bot.client,
|
||||
source.targetId,
|
||||
source.sequenceId,
|
||||
source.random,
|
||||
source.internalId,
|
||||
source.time
|
||||
).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
|
||||
}
|
||||
@ -378,7 +380,7 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
source.targetId, // groupUin
|
||||
source.targetId, // memberUin
|
||||
source.sequenceId,
|
||||
source.random,
|
||||
source.internalId,
|
||||
source.time
|
||||
).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
|
||||
}
|
||||
@ -387,7 +389,7 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
bot.client,
|
||||
source.targetId,
|
||||
source.sequenceId,
|
||||
source.random
|
||||
source.internalId
|
||||
).sendAndExpect<PbMessageSvc.PbMsgWithDraw.Response>()
|
||||
}
|
||||
}
|
||||
@ -658,6 +660,43 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
else -> error("unsupported image class: ${image::class.simpleName}")
|
||||
}
|
||||
|
||||
override fun constructMessageSource(
|
||||
kind: OfflineMessageSource.Kind,
|
||||
fromUin: Long,
|
||||
targetUin: Long,
|
||||
id: Int,
|
||||
time: Int,
|
||||
internalId: Int,
|
||||
originalMessage: MessageChain
|
||||
): OfflineMessageSource {
|
||||
return object : OfflineMessageSource(), MessageSourceInternal {
|
||||
override val kind: Kind get() = kind
|
||||
override val id: Int get() = id
|
||||
override val bot: Bot get() = this@QQAndroidBotBase
|
||||
override val time: Int get() = time
|
||||
override val fromId: Long get() = fromUin
|
||||
override val targetId: Long get() = targetUin
|
||||
override val originalMessage: MessageChain get() = originalMessage
|
||||
override val sequenceId: Int = id
|
||||
override val internalId: Int = internalId
|
||||
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
|
||||
|
||||
override fun toJceData(): ImMsgBody.SourceMsg {
|
||||
return ImMsgBody.SourceMsg(
|
||||
origSeqs = listOf(sequenceId),
|
||||
senderUin = fromUin,
|
||||
toUin = 0,
|
||||
flag = 1,
|
||||
elems = originalMessage.toRichTextElems(forGroup = kind == Kind.GROUP, withGeneralFlags = false),
|
||||
type = 0,
|
||||
time = time,
|
||||
pbReserve = EMPTY_BYTE_ARRAY,
|
||||
srcMsg = EMPTY_BYTE_ARRAY
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("use your own Http clients, this is going to be removed in 1.0.0", level = DeprecationLevel.WARNING)
|
||||
@ -673,6 +712,8 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
.fold(5381) { acc: Int, b: Byte -> acc + acc.shl(5) + b.toInt() }
|
||||
}
|
||||
|
||||
internal val EMPTY_BYTE_ARRAY = ByteArray(0)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
internal expect fun io.ktor.utils.io.ByteReadChannel.toKotlinByteReadChannel(): ByteReadChannel
|
||||
|
@ -27,7 +27,7 @@ import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
|
||||
|
||||
internal interface MessageSourceInternal {
|
||||
val sequenceId: Int
|
||||
val random: Int
|
||||
val internalId: Int // randomId
|
||||
|
||||
@Deprecated("don't use this internally. Use sequenceId or random instead.", level = DeprecationLevel.ERROR)
|
||||
val id: Int
|
||||
@ -54,12 +54,12 @@ internal class MessageSourceFromFriendImpl(
|
||||
override val sequenceId: Int get() = msg.msgHead.msgSeq
|
||||
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
|
||||
override val id: Int get() = sequenceId// msg.msgBody.richText.attr!!.random
|
||||
override val random: Int get() = msg.msgBody.richText.attr!!.random
|
||||
override val internalId: Int get() = msg.msgBody.richText.attr!!.random
|
||||
override val time: Int get() = msg.msgHead.msgTime
|
||||
override val originalMessage: MessageChain by lazy { msg.toMessageChain(bot, 0, false) }
|
||||
override val sender: Friend get() = bot.getFriend(msg.msgHead.fromUin)
|
||||
|
||||
private val jceData by lazy { msg.toJceDataFriendOrTemp(random) }
|
||||
private val jceData by lazy { msg.toJceDataFriendOrTemp(internalId) }
|
||||
|
||||
override fun toJceData(): ImMsgBody.SourceMsg = jceData
|
||||
}
|
||||
@ -105,14 +105,14 @@ internal class MessageSourceFromTempImpl(
|
||||
private val msg: MsgComm.Msg
|
||||
) : OnlineMessageSource.Incoming.FromTemp(), MessageSourceInternal {
|
||||
override val sequenceId: Int get() = msg.msgHead.msgSeq
|
||||
override val random: Int get() = msg.msgBody.richText.attr!!.random
|
||||
override val internalId: Int get() = msg.msgBody.richText.attr!!.random
|
||||
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
|
||||
override val id: Int get() = sequenceId//
|
||||
override val time: Int get() = msg.msgHead.msgTime
|
||||
override val originalMessage: MessageChain by lazy { msg.toMessageChain(bot, 0, false) }
|
||||
override val sender: Member get() = with(msg.msgHead) { bot.getGroup(c2cTmpMsgHead!!.groupUin)[fromUin] }
|
||||
|
||||
private val jceData by lazy { msg.toJceDataFriendOrTemp(random) }
|
||||
private val jceData by lazy { msg.toJceDataFriendOrTemp(internalId) }
|
||||
override fun toJceData(): ImMsgBody.SourceMsg = jceData
|
||||
}
|
||||
|
||||
@ -122,7 +122,7 @@ internal data class MessageSourceFromGroupImpl(
|
||||
) : OnlineMessageSource.Incoming.FromGroup(), MessageSourceInternal {
|
||||
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
|
||||
override val sequenceId: Int get() = msg.msgHead.msgSeq
|
||||
override val random: Int get() = msg.msgBody.richText.attr!!.random
|
||||
override val internalId: Int get() = msg.msgBody.richText.attr!!.random
|
||||
override val id: Int get() = sequenceId
|
||||
override val time: Int get() = msg.msgHead.msgTime
|
||||
override val originalMessage: MessageChain by lazy {
|
||||
|
@ -28,7 +28,7 @@ internal class OfflineMessageSourceImplByMsg(
|
||||
) : OfflineMessageSource(), MessageSourceInternal {
|
||||
override val kind: Kind = if (delegate.msgHead.groupInfo != null) Kind.GROUP else Kind.FRIEND
|
||||
override val id: Int get() = sequenceId
|
||||
override val random: Int
|
||||
override val internalId: Int
|
||||
get() = delegate.msgHead.msgUid.toInt()
|
||||
override val time: Int
|
||||
get() = delegate.msgHead.msgTime
|
||||
@ -74,7 +74,7 @@ internal class OfflineMessageSourceImplBySourceMsg(
|
||||
override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false)
|
||||
override val sequenceId: Int
|
||||
get() = delegate.origSeqs?.first() ?: error("cannot find sequenceId")
|
||||
override val random: Int
|
||||
override val internalId: Int
|
||||
get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids?.toInt() ?: 0
|
||||
override val time: Int get() = delegate.time
|
||||
override val originalMessage: MessageChain by lazy { delegate.toMessageChain(bot, groupIdOrZero) }
|
||||
|
@ -33,7 +33,7 @@ private fun <T> T.toJceDataImpl(): ImMsgBody.SourceMsg
|
||||
where T : MessageSourceInternal, T : MessageSource {
|
||||
|
||||
val elements = originalMessage.toRichTextElems(forGroup = false, withGeneralFlags = true)
|
||||
val messageUid: Long = sequenceId.toLong().shl(32) or random.toLong().and(0xffFFffFF)
|
||||
val messageUid: Long = sequenceId.toLong().shl(32) or internalId.toLong().and(0xffFFffFF)
|
||||
return ImMsgBody.SourceMsg(
|
||||
origSeqs = listOf(sequenceId),
|
||||
senderUin = fromId,
|
||||
@ -70,7 +70,7 @@ private fun <T> T.toJceDataImpl(): ImMsgBody.SourceMsg
|
||||
|
||||
internal class MessageSourceToFriendImpl(
|
||||
override val sequenceId: Int,
|
||||
override val random: Int,
|
||||
override val internalId: Int,
|
||||
override val time: Int,
|
||||
override val originalMessage: MessageChain,
|
||||
override val sender: Bot,
|
||||
@ -87,7 +87,7 @@ internal class MessageSourceToFriendImpl(
|
||||
|
||||
internal class MessageSourceToTempImpl(
|
||||
override val sequenceId: Int,
|
||||
override val random: Int,
|
||||
override val internalId: Int,
|
||||
override val time: Int,
|
||||
override val originalMessage: MessageChain,
|
||||
override val sender: Bot,
|
||||
@ -104,7 +104,7 @@ internal class MessageSourceToTempImpl(
|
||||
|
||||
internal class MessageSourceToGroupImpl(
|
||||
coroutineScope: CoroutineScope,
|
||||
override val random: Int,
|
||||
override val internalId: Int,
|
||||
override val time: Int,
|
||||
override val originalMessage: MessageChain,
|
||||
override val sender: Bot,
|
||||
@ -120,7 +120,7 @@ internal class MessageSourceToGroupImpl(
|
||||
coroutineScope.asyncFromEventOrNull<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(
|
||||
timeoutMillis = 3000
|
||||
) {
|
||||
if (it.messageRandom == this@MessageSourceToGroupImpl.random) {
|
||||
if (it.messageRandom == this@MessageSourceToGroupImpl.internalId) {
|
||||
it.sequenceId
|
||||
} else null
|
||||
}
|
||||
|
@ -369,7 +369,7 @@ internal class MessageSvc {
|
||||
): OutgoingPacket {
|
||||
val rand = Random.nextInt().absoluteValue
|
||||
val source = MessageSourceToFriendImpl(
|
||||
random = rand,
|
||||
internalId = rand,
|
||||
sender = client.bot,
|
||||
target = qq,
|
||||
time = currentTimeSeconds.toInt(),
|
||||
@ -403,7 +403,7 @@ internal class MessageSvc {
|
||||
)
|
||||
),
|
||||
msgSeq = source.sequenceId,
|
||||
msgRand = source.random,
|
||||
msgRand = source.internalId,
|
||||
syncCookie = SyncCookie(time = source.time.toLong()).toByteArray(SyncCookie.serializer())
|
||||
// msgVia = 1
|
||||
)
|
||||
@ -418,7 +418,7 @@ internal class MessageSvc {
|
||||
sourceCallback: (MessageSourceToTempImpl) -> Unit
|
||||
): OutgoingPacket {
|
||||
val source = MessageSourceToTempImpl(
|
||||
random = Random.nextInt().absoluteValue,
|
||||
internalId = Random.nextInt().absoluteValue,
|
||||
sender = client.bot,
|
||||
target = member,
|
||||
time = currentTimeSeconds.toInt(),
|
||||
@ -451,7 +451,7 @@ internal class MessageSvc {
|
||||
)
|
||||
),
|
||||
msgSeq = source.sequenceId,
|
||||
msgRand = source.random,
|
||||
msgRand = source.internalId,
|
||||
syncCookie = SyncCookie(time = source.time.toLong()).toByteArray(SyncCookie.serializer())
|
||||
)
|
||||
)
|
||||
@ -467,7 +467,7 @@ internal class MessageSvc {
|
||||
|
||||
val source = MessageSourceToGroupImpl(
|
||||
group,
|
||||
random = Random.nextInt().absoluteValue,
|
||||
internalId = Random.nextInt().absoluteValue,
|
||||
sender = client.bot,
|
||||
target = group,
|
||||
time = currentTimeSeconds.toInt(),
|
||||
@ -503,7 +503,7 @@ internal class MessageSvc {
|
||||
)
|
||||
),
|
||||
msgSeq = client.atomicNextMessageSequenceId(),
|
||||
msgRand = source.random,
|
||||
msgRand = source.internalId,
|
||||
syncCookie = EMPTY_BYTE_ARRAY,
|
||||
msgVia = 1
|
||||
)
|
||||
|
@ -179,6 +179,24 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
@JvmSynthetic
|
||||
abstract suspend fun queryImageUrl(image: Image): String
|
||||
|
||||
/**
|
||||
* 构造一个 [OfflineMessageSource]
|
||||
*
|
||||
* @param id 即 [MessageSource.id]
|
||||
* @param internalId 即 [MessageSource.internalId]
|
||||
*
|
||||
* @param fromUin 为用户时为 [Friend.id], 为群时需使用 [Group.calculateGroupUinByGroupCode] 计算
|
||||
* @param targetUin 为用户时为 [Friend.id], 为群时需使用 [Group.calculateGroupUinByGroupCode] 计算
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("0.39.0")
|
||||
abstract fun constructMessageSource(
|
||||
kind: OfflineMessageSource.Kind,
|
||||
fromUin: Long, targetUin: Long,
|
||||
id: Int, time: Int, internalId: Int,
|
||||
originalMessage: MessageChain
|
||||
): OfflineMessageSource
|
||||
|
||||
/**
|
||||
* 获取图片下载链接并开始下载.
|
||||
*
|
||||
@ -281,6 +299,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
val selfQQDeprecated: QQ
|
||||
get() = selfQQ
|
||||
|
||||
@PlannedRemoval("1.0.0.")
|
||||
@JvmName("getFriend")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
|
@ -28,11 +28,9 @@ import net.mamoe.mirai.utils.*
|
||||
*
|
||||
* 目前在回复时无法通过 [originalMessage] 获取自定义类型消息
|
||||
*
|
||||
* **实现方法**:
|
||||
*
|
||||
* @sample samples.CustomMessageIdentifier 实现示例
|
||||
*
|
||||
* @see CustomMessageMetadata
|
||||
* @see CustomMessageMetadata 自定义消息元数据
|
||||
*/
|
||||
@SinceMirai("0.38.0")
|
||||
@MiraiExperimentalAPI
|
||||
@ -172,6 +170,11 @@ sealed class CustomMessage : SingleMessage {
|
||||
/**
|
||||
* 自定义消息元数据.
|
||||
*
|
||||
* **实现方法**:
|
||||
* 1. 实现一个类继承 [CustomMessageMetadata], 添加 `@Serializable` (来自 `kotlinx.serialization`)
|
||||
* 2. 添加伴生对象, 继承 [CustomMessage.ProtoBufSerializerFactory] 或 [CustomMessage.JsonSerializerFactory], 或 [CustomMessage.Factory]
|
||||
* 3. 在需要解析消息前调用一次伴生对象以注册
|
||||
*
|
||||
* @see CustomMessage 查看更多信息
|
||||
* @see ConstrainSingle 可实现此接口以保证消息链中只存在一个元素
|
||||
*/
|
||||
@ -191,8 +194,9 @@ abstract class CustomMessageMetadata : CustomMessage(), MessageMetadata {
|
||||
}
|
||||
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
internal fun <T : CustomMessageMetadata> T.customToStringImpl(factory: CustomMessage.Factory<*>): ByteArray {
|
||||
internal inline fun <T : CustomMessageMetadata> T.customToStringImpl(factory: CustomMessage.Factory<*>): ByteArray {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return (factory as CustomMessage.Factory<T>).serialize(this)
|
||||
}
|
@ -58,6 +58,7 @@ import kotlin.jvm.JvmSynthetic
|
||||
* @see QuoteReply 一条消息的引用
|
||||
* @see RichMessage 富文本消息, 如 [Xml][XmlMessage], [小程序][LightApp], [Json][JsonMessage]
|
||||
* @see HummerMessage 一些特殊的消息, 如 [闪照][FlashImage], [戳一戳][PokeMessage]
|
||||
* @see CustomMessage 自定义消息类型
|
||||
*
|
||||
* @see MessageChain 消息链(即 `List<Message>`)
|
||||
* @see buildMessageChain 构造一个 [MessageChain]
|
||||
|
@ -62,8 +62,11 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSo
|
||||
|
||||
/**
|
||||
* 内部 id, 仅用于 [Bot.constructMessageSource]
|
||||
* 可能为 0, 取决于服务器是否提供.
|
||||
* 值没有顺序, 也可能为 0, 取决于服务器是否提供.
|
||||
*
|
||||
* 仅用于协议实现.
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
abstract val internalId: Int
|
||||
|
||||
/**
|
||||
@ -94,7 +97,7 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSo
|
||||
@LazyProperty
|
||||
abstract val originalMessage: MessageChain
|
||||
|
||||
final override fun toString(): String = "[mirai:source:$id]"
|
||||
final override fun toString(): String = "[mirai:source:$id,$internalId]"
|
||||
final override fun contentToString(): String = ""
|
||||
}
|
||||
|
||||
|
@ -13,13 +13,14 @@
|
||||
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.BotImpl
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.ContactOrBot
|
||||
import net.mamoe.mirai.contact.Friend
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import net.mamoe.mirai.utils.asSequence
|
||||
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
@ -53,68 +54,231 @@ abstract class OfflineMessageSource : MessageSource() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 复制这个消息源, 并修改
|
||||
*/
|
||||
@JvmName("copySource")
|
||||
inline fun MessageSource.copyAmend(
|
||||
block: MessageSourceBuilder.() -> Unit
|
||||
): OfflineMessageSource {
|
||||
return constructMessageSource()
|
||||
}
|
||||
///////////////
|
||||
//// AMEND ////
|
||||
///////////////
|
||||
|
||||
|
||||
/**
|
||||
* 复制这个消息源, 并以 [block] 修改
|
||||
*
|
||||
* @see buildMessageSource 查看更多说明
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("0.39.0")
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
fun constructMessageSource(
|
||||
kind: OfflineMessageSource.Kind,
|
||||
fromUin: Long, targetUin: Long,
|
||||
id: Int, time: Int, internalId: Int,
|
||||
originalMessage: MessageChain
|
||||
): OfflineMessageSource {
|
||||
val bot = BotImpl.instances.asSequence().mapNotNull { it.get() }.firstOrNull()
|
||||
?: error("no Bot instance available")
|
||||
@JvmName("copySource")
|
||||
fun MessageSource.copyAmend(
|
||||
block: MessageSourceAmender.() -> Unit
|
||||
): OfflineMessageSource = toMutableOffline().apply(block)
|
||||
|
||||
return bot.constructMessageSource(kind, fromUin, targetUin, id, time, internalId, originalMessage)
|
||||
/**
|
||||
* 仅于 [copyAmend] 中修改 [MessageSource]
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
interface MessageSourceAmender {
|
||||
var kind: OfflineMessageSource.Kind
|
||||
var fromUin: Long
|
||||
var targetUin: Long
|
||||
var id: Int
|
||||
var time: Int
|
||||
var internalId: Int
|
||||
|
||||
var originalMessage: MessageChain
|
||||
}
|
||||
|
||||
|
||||
///////////////
|
||||
//// BUILD ////
|
||||
///////////////
|
||||
|
||||
|
||||
/**
|
||||
* 构建一个 [OfflineMessageSource]
|
||||
*
|
||||
* ### 参数
|
||||
* 一个 [OfflineMessageSource] 须要以下参数:
|
||||
* - 发送人和发送目标: 通过 [MessageSourceBuilder.sendTo] 设置
|
||||
* - 消息元数据 (即 [MessageSource.id], [MessageSource.internalId], [MessageSource.time])
|
||||
* 元数据用于 [撤回][MessageSource.recall], [引用回复][MessageSource.quote], 和官方客户端定位原消息.
|
||||
* 可通过 [MessageSourceBuilder.id], [MessageSourceBuilder.time], [MessageSourceBuilder.internalId] 设置
|
||||
* 可通过 [MessageSourceBuilder.metadata] 从另一个 [MessageSource] 复制
|
||||
* - 消息内容: 通过 [MessageSourceBuilder.messages] 设置
|
||||
*
|
||||
* ### 性质
|
||||
* - 当两个消息的元数据相同时, 他们在群中会是同一条消息. 可通过此特性决定官方客户端 "定位原消息" 的目标
|
||||
* - 发送人的信息和消息内容会在官方客户端显示在引用回复中.
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmSynthetic
|
||||
@MiraiExperimentalAPI
|
||||
inline fun buildMessageSource(block: MessageSourceBuilder.() -> Unit): MessageSource {
|
||||
val builder = MessageSourceBuilder().apply(block)
|
||||
fun Bot.buildMessageSource(block: MessageSourceBuilder.() -> Unit): MessageSource {
|
||||
val builder = MessageSourceBuilderImpl().apply(block)
|
||||
return constructMessageSource(
|
||||
builder.kind ?: error("found "),
|
||||
block
|
||||
builder.kind ?: error("You must call `Contact.sendTo(Contact)` when `buildMessageSource`"),
|
||||
builder.fromUin,
|
||||
builder.targetUin,
|
||||
builder.id,
|
||||
builder.time,
|
||||
builder.internalId,
|
||||
builder.originalMessages.build()
|
||||
)
|
||||
}
|
||||
|
||||
@DslMarker
|
||||
annotation class SourceBuilderDsl
|
||||
/**
|
||||
* @see buildMessageSource
|
||||
*/
|
||||
abstract class MessageSourceBuilder {
|
||||
internal abstract var kind: OfflineMessageSource.Kind?
|
||||
internal abstract var fromUin: Long
|
||||
internal abstract var targetUin: Long
|
||||
|
||||
class MessageSourceBuilder(
|
||||
source: OfflineMessageSource
|
||||
) : MessageChainBuilder() {
|
||||
var kind: OfflineMessageSource.Kind = source.kind
|
||||
var fromUin: Long = source.fromId
|
||||
var targetUin: Long = source.targetId
|
||||
var id: Int = source.id
|
||||
var time: Int = source.time
|
||||
var internalId: Int = source.internalId
|
||||
var originalMessage: MessageChain = source.originalMessage
|
||||
internal abstract var id: Int
|
||||
internal abstract var time: Int
|
||||
internal abstract var internalId: Int
|
||||
|
||||
fun from(sender: Contact): MessageSourceBuilder {
|
||||
fromUin = if (sender is Group) {
|
||||
Group.calculateGroupUinByGroupCode(sender.id)
|
||||
} else sender.id
|
||||
@PublishedApi
|
||||
internal val originalMessages: MessageChainBuilder = MessageChainBuilder()
|
||||
|
||||
fun time(from: MessageSource): MessageSourceBuilder = apply { this.time = from.time }
|
||||
val now: Int get() = currentTimeSeconds.toInt()
|
||||
fun time(value: Int) = apply { this.time = value }
|
||||
|
||||
fun internalId(from: MessageSource): MessageSourceBuilder = apply { this.internalId = from.internalId }
|
||||
fun internalId(value: Int): MessageSourceBuilder = apply { this.internalId = value }
|
||||
|
||||
fun id(from: MessageSource): MessageSourceBuilder = apply { this.id = from.id }
|
||||
fun id(value: Int): MessageSourceBuilder = apply { this.id = value }
|
||||
|
||||
|
||||
/**
|
||||
* 从另一个 [MessageSource] 复制 [id], [time], [internalId].
|
||||
* 这三个数据决定官方客户端能 "定位" 到的原消息
|
||||
*/
|
||||
fun metadata(from: MessageSource): MessageSourceBuilder = apply {
|
||||
id(from)
|
||||
internalId(from)
|
||||
time(from)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从另一个 [MessageSource] 复制所有信息, 包括消息内容. 不会清空已有消息.
|
||||
*/
|
||||
fun allFrom(source: MessageSource): MessageSourceBuilder {
|
||||
this.kind = determineKind(source)
|
||||
this.id = source.id
|
||||
this.time = source.time
|
||||
this.fromUin = source.fromId
|
||||
this.targetUin = source.targetId
|
||||
this.internalId = source.internalId
|
||||
this.originalMessages.addAll(source.originalMessage)
|
||||
return this
|
||||
}
|
||||
|
||||
fun target(target: Contact): MessageSourceBuilder {
|
||||
|
||||
/**
|
||||
* 从另一个 [MessageSource] 复制 [消息内容][MessageSource.originalMessage]. 不会清空已有消息.
|
||||
*/
|
||||
fun messagesFrom(source: MessageSource): MessageSourceBuilder = apply {
|
||||
this.originalMessages.addAll(source.originalMessage)
|
||||
}
|
||||
|
||||
fun messages(messages: Iterable<Message>): MessageSourceBuilder = apply {
|
||||
this.originalMessages.addAll(messages)
|
||||
}
|
||||
|
||||
fun messages(vararg message: Message): MessageSourceBuilder = apply {
|
||||
for (it in message) {
|
||||
this.originalMessages.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
inline fun messages(block: MessageChainBuilder.() -> Unit): MessageSourceBuilder = apply {
|
||||
this.originalMessages.apply(block)
|
||||
}
|
||||
|
||||
fun clearMessages(): MessageSourceBuilder = apply { this.originalMessages.clear() }
|
||||
|
||||
/**
|
||||
* 设置 [发送人][this] 和 [发送目标][target], 并自动判断 [kind]
|
||||
*/
|
||||
@JvmSynthetic
|
||||
abstract infix fun ContactOrBot.sendTo(target: ContactOrBot): MessageSourceBuilder
|
||||
|
||||
fun setSenderAndTarget(sender: ContactOrBot, target: ContactOrBot) = sender sendTo target
|
||||
}
|
||||
|
||||
|
||||
//////////////////
|
||||
//// INTERNAL ////
|
||||
//////////////////
|
||||
|
||||
|
||||
internal class MessageSourceBuilderImpl : MessageSourceBuilder() {
|
||||
override var kind: OfflineMessageSource.Kind? = null
|
||||
override var fromUin: Long = 0
|
||||
override var targetUin: Long = 0
|
||||
|
||||
override var id: Int = 0
|
||||
override var time: Int = currentTimeSeconds.toInt()
|
||||
override var internalId: Int = 0
|
||||
|
||||
@JvmSynthetic
|
||||
override fun ContactOrBot.sendTo(target: ContactOrBot): MessageSourceBuilder {
|
||||
fromUin = if (this is Group) {
|
||||
Group.calculateGroupUinByGroupCode(this.id)
|
||||
} else this.id
|
||||
|
||||
targetUin = if (target is Group) {
|
||||
Group.calculateGroupUinByGroupCode(target.id)
|
||||
} else target.id
|
||||
return this
|
||||
}
|
||||
|
||||
fun
|
||||
check(this != target) { "sender and target mustn't be the same" }
|
||||
|
||||
kind = when {
|
||||
this is Group || target is Group -> OfflineMessageSource.Kind.GROUP
|
||||
this is Member || target is Member -> OfflineMessageSource.Kind.TEMP
|
||||
this is Bot && target is Friend -> OfflineMessageSource.Kind.FRIEND
|
||||
this is Friend && target is Bot -> OfflineMessageSource.Kind.FRIEND
|
||||
else -> throw IllegalArgumentException("Cannot determine source kind for sender $this and target $target")
|
||||
}
|
||||
return this@MessageSourceBuilderImpl
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun MessageSource.toMutableOffline(): MutableOfflineMessageSourceByOnline =
|
||||
MutableOfflineMessageSourceByOnline(this)
|
||||
|
||||
internal class MutableOfflineMessageSourceByOnline(
|
||||
origin: MessageSource
|
||||
) : OfflineMessageSource(), MessageSourceAmender {
|
||||
override var kind: Kind = determineKind(origin)
|
||||
override var fromUin: Long
|
||||
get() = fromId
|
||||
set(value) {
|
||||
fromId = value
|
||||
}
|
||||
override var targetUin: Long
|
||||
get() = targetId
|
||||
set(value) {
|
||||
targetId = value
|
||||
}
|
||||
override var bot: Bot = origin.bot
|
||||
override var id: Int = origin.id
|
||||
override var internalId: Int = origin.internalId
|
||||
override var time: Int = origin.time
|
||||
override var fromId: Long = origin.fromId
|
||||
override var targetId: Long = origin.targetId
|
||||
override var originalMessage: MessageChain = origin.originalMessage
|
||||
}
|
||||
|
||||
private fun determineKind(source: MessageSource): OfflineMessageSource.Kind {
|
||||
return when {
|
||||
source.isAboutGroup() -> OfflineMessageSource.Kind.GROUP
|
||||
source.isAboutFriend() -> OfflineMessageSource.Kind.FRIEND
|
||||
source.isAboutTemp() -> OfflineMessageSource.Kind.TEMP
|
||||
else -> error("stub")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user