mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-05 07:30:09 +08:00
Support ForwardMessage DSL
This commit is contained in:
parent
6734403fb0
commit
1b4e1475a0
@ -552,12 +552,14 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
@MiraiExperimentalAPI
|
||||
internal suspend fun lowLevelSendGroupLongOrForwardMessage(
|
||||
groupCode: Long,
|
||||
message: Collection<MessageChain>,
|
||||
isLong: Boolean
|
||||
message: Collection<ForwardMessage.INode>,
|
||||
isLong: Boolean,
|
||||
forwardMessage: ForwardMessage?
|
||||
): MessageReceipt<Group> {
|
||||
message.forEach {
|
||||
it.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
it.message.ensureSequenceIdAvailable()
|
||||
}
|
||||
|
||||
val group = getGroup(groupCode)
|
||||
|
||||
val time = currentTimeSeconds
|
||||
@ -566,10 +568,8 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
network.run {
|
||||
val data = message.calculateValidationDataForGroup(
|
||||
sequenceId = sequenceId,
|
||||
time = time.toInt(),
|
||||
random = Random.nextInt().absoluteValue.toUInt(),
|
||||
groupCode = groupCode,
|
||||
botId = this@QQAndroidBotBase.id,
|
||||
botMemberNameCard = group.botAsMember.nameCardOrNick
|
||||
)
|
||||
|
||||
@ -646,21 +646,30 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
return if (isLong) {
|
||||
group.sendMessage(
|
||||
RichMessage.longMessage(
|
||||
brief = message.joinToString(limit = 27) { it.contentToString() },
|
||||
brief = message.joinToString(limit = 27) { it.message.contentToString() },
|
||||
resId = resId,
|
||||
timeSeconds = time
|
||||
)
|
||||
)
|
||||
} else {
|
||||
checkNotNull(forwardMessage) { "Internal error: forwardMessage is null when sending forward" }
|
||||
group.sendMessage(
|
||||
RichMessage.forwardMessage(
|
||||
resId = resId,
|
||||
timeSeconds = time,
|
||||
preview = message.take(3).joinToString {
|
||||
"""
|
||||
<title size="26" color="#777777" maxLines="2" lineSpace="12">${it.joinToString(limit = 10)}</title>
|
||||
""".trimIndent()
|
||||
}
|
||||
// preview = message.take(5).joinToString {
|
||||
// """
|
||||
// <title size="26" color="#777777" maxLines="2" lineSpace="12">${it.message.asMessageChain().joinToString(limit = 10)}</title>
|
||||
// """.trimIndent()
|
||||
// },
|
||||
preview = forwardMessage.displayStrategy.generatePreview(forwardMessage).take(4)
|
||||
.map {
|
||||
"""<title size="26" color="#777777" maxLines="2" lineSpace="12">$it</title>"""
|
||||
}.joinToString(""),
|
||||
title = forwardMessage.displayStrategy.generateTitle(forwardMessage),
|
||||
brief = forwardMessage.displayStrategy.generateBrief(forwardMessage),
|
||||
source = forwardMessage.displayStrategy.generateSource(forwardMessage),
|
||||
summary = forwardMessage.displayStrategy.generateSummary(forwardMessage)
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -769,21 +778,25 @@ private fun RichMessage.Templates.longMessage(brief: String, resId: String, time
|
||||
private fun RichMessage.Templates.forwardMessage(
|
||||
resId: String,
|
||||
timeSeconds: Long,
|
||||
preview: String
|
||||
preview: String,
|
||||
title: String,
|
||||
brief: String,
|
||||
source: String,
|
||||
summary: String
|
||||
): ForwardMessageInternal {
|
||||
val template = """
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<msg serviceID="35" templateID="1" action="viewMultiMsg" brief="[聊天记录]"
|
||||
<msg serviceID="35" templateID="1" action="viewMultiMsg" brief="$brief"
|
||||
m_resid="$resId" m_fileName="$timeSeconds"
|
||||
tSum="3" sourceMsgId="0" url="" flag="3" adverSign="0" multiMsgFlag="0">
|
||||
<item layout="1" advertiser_id="0" aid="0">
|
||||
<title size="34" maxLines="2" lineSpace="12">群聊的聊天记录</title>
|
||||
<title size="34" maxLines="2" lineSpace="12">$title</title>
|
||||
$preview
|
||||
<hr hidden="false" style="0"/>
|
||||
<summary size="26" color="#777777">查看3条转发消息</summary>
|
||||
<summary size="26" color="#777777">$summary</summary>
|
||||
</item>
|
||||
<source name="聊天记录" icon="" action="" appid="-1"/>
|
||||
<source name="$source" icon="" action="" appid="-1"/>
|
||||
</msg>
|
||||
""".trimIndent()
|
||||
""".trimIndent().replace("\n", " ")
|
||||
return ForwardMessageInternal(template)
|
||||
}
|
@ -295,7 +295,13 @@ internal class GroupImpl(
|
||||
}
|
||||
}
|
||||
if (message is ForwardMessage) {
|
||||
return bot.lowLevelSendGroupLongOrForwardMessage(this.id, message.messageList, false)
|
||||
check(message.nodeList.size < 200) {
|
||||
throw MessageTooLargeException(
|
||||
this, message, message,
|
||||
"ForwardMessage allows up to 200 nodes, but found ${message.nodeList.size}")
|
||||
}
|
||||
|
||||
return bot.lowLevelSendGroupLongOrForwardMessage(this.id, message.nodeList, false, message)
|
||||
}
|
||||
|
||||
val msg: MessageChain
|
||||
@ -321,8 +327,16 @@ internal class GroupImpl(
|
||||
)
|
||||
}
|
||||
|
||||
if (length > 702 || imageCnt > 2)
|
||||
return bot.lowLevelSendGroupLongOrForwardMessage(this.id, listOf(event.message), true)
|
||||
if (length > 702 || imageCnt > 2) {
|
||||
return bot.lowLevelSendGroupLongOrForwardMessage(this.id,
|
||||
listOf(ForwardMessage.Node(
|
||||
senderId = bot.id,
|
||||
time = currentTimeSeconds.toInt(),
|
||||
message = event.message,
|
||||
senderName = bot.nick)
|
||||
),
|
||||
true, null)
|
||||
}
|
||||
|
||||
msg = event.message
|
||||
} else msg = message.asMessageChain()
|
||||
@ -343,7 +357,15 @@ internal class GroupImpl(
|
||||
120 -> throw BotIsBeingMutedException(this@GroupImpl)
|
||||
34 -> {
|
||||
kotlin.runCatching { // allow retry once
|
||||
return bot.lowLevelSendGroupLongOrForwardMessage(id, listOf(msg), true)
|
||||
return bot.lowLevelSendGroupLongOrForwardMessage(
|
||||
id, listOf(
|
||||
ForwardMessage.Node(
|
||||
senderId = bot.id,
|
||||
time = currentTimeSeconds.toInt(),
|
||||
message = msg,
|
||||
senderName = bot.nick
|
||||
)
|
||||
), true, null)
|
||||
}.getOrElse {
|
||||
throw IllegalStateException("internal error: send message failed(34)", it)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Friend
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.event.internal.MiraiAtomicBoolean
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.message.data.OnlineMessageSource
|
||||
@ -47,6 +48,18 @@ internal suspend inline fun MessageSource.ensureSequenceIdAvailable() {
|
||||
}*/
|
||||
}
|
||||
|
||||
@Suppress("RedundantSuspendModifier", "unused")
|
||||
internal suspend inline fun Message.ensureSequenceIdAvailable() {
|
||||
// no suspend.
|
||||
|
||||
// obsolete but keep for future
|
||||
return
|
||||
/*
|
||||
if (this is MessageSourceToGroupImpl) {
|
||||
this.ensureSequenceIdAvailable()
|
||||
}*/
|
||||
}
|
||||
|
||||
internal class MessageSourceFromFriendImpl(
|
||||
override val bot: Bot,
|
||||
val msg: MsgComm.Msg
|
||||
|
@ -9,32 +9,32 @@ import kotlin.jvm.JvmField
|
||||
@Serializable
|
||||
internal class MultiMsg : ProtoBuf {
|
||||
@Serializable
|
||||
internal class ExternMsg(
|
||||
internal class ExternMsg(
|
||||
@ProtoId(1) @JvmField val channelType: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class MultiMsgApplyDownReq(
|
||||
internal class MultiMsgApplyDownReq(
|
||||
@ProtoId(1) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@ProtoId(2) @JvmField val msgType: Int = 0,
|
||||
@ProtoId(3) @JvmField val srcUin: Long = 0L
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class MultiMsgApplyDownRsp(
|
||||
internal class MultiMsgApplyDownRsp(
|
||||
@ProtoId(1) @JvmField val result: Int = 0,
|
||||
@ProtoId(2) @JvmField val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@ProtoId(3) @JvmField val msgKey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@ProtoId(4) @JvmField val uint32DownIp: List<Int>? = null,
|
||||
@ProtoId(5) @JvmField val uint32DownPort: List<Int>? = null,
|
||||
@ProtoId(6) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@ProtoId(7) @JvmField val msgExternInfo: MultiMsg.ExternMsg? = null,
|
||||
@ProtoId(7) @JvmField val msgExternInfo: ExternMsg? = null,
|
||||
@ProtoId(8) @JvmField val bytesDownIpV6: List<ByteArray>? = null,
|
||||
@ProtoId(9) @JvmField val uint32DownV6Port: List<Int>? = null
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class MultiMsgApplyUpReq(
|
||||
internal class MultiMsgApplyUpReq(
|
||||
@ProtoId(1) @JvmField val dstUin: Long = 0L,
|
||||
@ProtoId(2) @JvmField val msgSize: Long = 0L,
|
||||
@ProtoId(3) @JvmField val msgMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@ -43,24 +43,24 @@ internal class MultiMsgApplyUpReq(
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class MultiMsgApplyUpRsp(
|
||||
internal class MultiMsgApplyUpRsp(
|
||||
@ProtoId(1) @JvmField val result: Int = 0,
|
||||
@ProtoId(2) @JvmField val msgResid: String = "",
|
||||
@ProtoId(3) @JvmField val msgUkey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@ProtoId(4) @JvmField val uint32UpIp: List<Int>,
|
||||
@ProtoId(5) @JvmField val uint32UpPort: List<Int>,
|
||||
@ProtoId(4) @JvmField val uint32UpIp: List<Int> = listOf(),
|
||||
@ProtoId(5) @JvmField val uint32UpPort: List<Int> = listOf(),
|
||||
@ProtoId(6) @JvmField val blockSize: Long = 0L,
|
||||
@ProtoId(7) @JvmField val upOffset: Long = 0L,
|
||||
@ProtoId(8) @JvmField val applyId: Int = 0,
|
||||
@ProtoId(9) @JvmField val msgKey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@ProtoId(10) @JvmField val msgSig: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@ProtoId(11) @JvmField val msgExternInfo: MultiMsg.ExternMsg? = null,
|
||||
@ProtoId(11) @JvmField val msgExternInfo: ExternMsg? = null,
|
||||
@ProtoId(12) @JvmField val bytesUpIpV6: List<ByteArray>? = null,
|
||||
@ProtoId(13) @JvmField val uint32UpV6Port: List<Int>? = null
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class ReqBody(
|
||||
internal class ReqBody(
|
||||
@ProtoId(1) @JvmField val subcmd: Int = 0,
|
||||
@ProtoId(2) @JvmField val termType: Int = 0,
|
||||
@ProtoId(3) @JvmField val platformType: Int = 0,
|
||||
@ -73,7 +73,7 @@ internal class ReqBody(
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class RspBody(
|
||||
internal class RspBody(
|
||||
@ProtoId(1) @JvmField val subcmd: Int = 0,
|
||||
@ProtoId(2) @JvmField val multimsgApplyupRsp: List<MultiMsg.MultiMsgApplyUpRsp>? = null,
|
||||
@ProtoId(3) @JvmField val multimsgApplydownRsp: List<MultiMsg.MultiMsgApplyDownRsp>? = null
|
||||
|
@ -12,7 +12,8 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.ForwardMessage
|
||||
import net.mamoe.mirai.message.data.asMessageChain
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.message.toRichTextElems
|
||||
import net.mamoe.mirai.qqandroid.network.Packet
|
||||
@ -42,12 +43,10 @@ internal class MessageValidationData @OptIn(MiraiInternalAPI::class) constructor
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
internal fun Collection<MessageChain>.calculateValidationDataForGroup(
|
||||
internal fun Collection<ForwardMessage.INode>.calculateValidationDataForGroup(
|
||||
sequenceId: Int,
|
||||
time: Int,
|
||||
random: UInt,
|
||||
groupCode: Long,
|
||||
botId: Long,
|
||||
botMemberNameCard: String
|
||||
): MessageValidationData {
|
||||
|
||||
@ -55,9 +54,9 @@ internal fun Collection<MessageChain>.calculateValidationDataForGroup(
|
||||
msg = this.map { chain ->
|
||||
MsgComm.Msg(
|
||||
msgHead = MsgComm.MsgHead(
|
||||
fromUin = botId,
|
||||
fromUin = chain.senderId,
|
||||
msgSeq = sequenceId,
|
||||
msgTime = time,
|
||||
msgTime = chain.time,
|
||||
msgUid = 0x01000000000000000L or random.toLong(),
|
||||
mutiltransHead = MsgComm.MutilTransHead(
|
||||
status = 0,
|
||||
@ -66,13 +65,14 @@ internal fun Collection<MessageChain>.calculateValidationDataForGroup(
|
||||
msgType = 82, // troop
|
||||
groupInfo = MsgComm.GroupInfo(
|
||||
groupCode = groupCode,
|
||||
groupCard = botMemberNameCard // Cinnamon
|
||||
groupCard = chain.senderName // Cinnamon
|
||||
),
|
||||
isSrcMsg = false
|
||||
),
|
||||
msgBody = ImMsgBody.MsgBody(
|
||||
richText = ImMsgBody.RichText(
|
||||
elems = chain.toRichTextElems(forGroup = true, withGeneralFlags = false).toMutableList()
|
||||
elems = chain.message.asMessageChain()
|
||||
.toRichTextElems(forGroup = true, withGeneralFlags = false).toMutableList()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -7,42 +7,553 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
@file:Suppress("MemberVisibilityCanBePrivate", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused")
|
||||
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.ContactMessage
|
||||
import net.mamoe.mirai.message.data.ForwardMessage.DisplayStrategy
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||
import kotlin.jvm.JvmOverloads
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
|
||||
/**
|
||||
* 合并转发
|
||||
* 合并转发消息
|
||||
*
|
||||
* @param [displayStrategy] 卡片显示方案
|
||||
*
|
||||
* ### 显示方案
|
||||
*
|
||||
* #### 移动端
|
||||
* 在移动客户端将会显示为卡片
|
||||
*
|
||||
* `<title>`: [DisplayStrategy.generateTitle]
|
||||
*
|
||||
* `<preview>`: [DisplayStrategy.generatePreview]
|
||||
*
|
||||
* `<summary>`: [DisplayStrategy.generateSummary]
|
||||
*
|
||||
* ```
|
||||
* |-------------------------|
|
||||
* | <title> |
|
||||
* | <preview> |
|
||||
* |-------------------------|
|
||||
* | <summary> |
|
||||
* |-------------------------|
|
||||
* ```
|
||||
*
|
||||
* 默认显示方案:
|
||||
* ```
|
||||
* |-------------------------|
|
||||
* | 群聊的聊天记录 |
|
||||
* | <消息 1> |
|
||||
* | <消息 2> |
|
||||
* | <消息 3> |
|
||||
* |-------------------------|
|
||||
* | 查看 3 条转发消息 |
|
||||
* |-------------------------|
|
||||
* ```
|
||||
*
|
||||
* #### PC 端
|
||||
* 在部分 PC 端显示为类似移动端的卡片, 在其他 PC 端显示为以下格式
|
||||
* ```
|
||||
* 鸽子 A 2020/04/23 11:27:54
|
||||
* 咕
|
||||
* 鸽子 B 2020/04/23 11:27:55
|
||||
* 咕
|
||||
* 鸽子 C 1970/01/01 08:00:00
|
||||
* 咕咕咕
|
||||
* ```
|
||||
*
|
||||
* ### 构造
|
||||
* - 使用 [DSL][buildForwardMessage]
|
||||
* - 通过 [ContactMessage] 集合转换: [toForwardMessage]
|
||||
*
|
||||
* @see buildForwardMessage
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
class ForwardMessage(
|
||||
val messageList: Collection<MessageChain>
|
||||
class ForwardMessage @JvmOverloads constructor(
|
||||
/**
|
||||
* 消息列表
|
||||
*/
|
||||
val nodeList: Collection<INode>,
|
||||
val displayStrategy: DisplayStrategy = DisplayStrategy
|
||||
) : MessageContent {
|
||||
/**
|
||||
* @see ForwardMessage
|
||||
*/
|
||||
abstract class DisplayStrategy {
|
||||
/**
|
||||
* 修改后卡片标题会变为 "转发的聊天记录", 而此函数的返回值会显示在 preview 前
|
||||
*/
|
||||
open fun generateTitle(forward: ForwardMessage): String = "群聊的聊天记录"
|
||||
|
||||
/**
|
||||
* 显示在消息列表中的预览.
|
||||
*/
|
||||
open fun generateBrief(forward: ForwardMessage): String = "[聊天记录]"
|
||||
|
||||
/**
|
||||
* 目前未发现在哪能显示
|
||||
*/
|
||||
open fun generateSource(forward: ForwardMessage): String = "聊天记录"
|
||||
|
||||
/**
|
||||
* 显示在卡片 body 中, 只会显示 sequence 前四个元素.
|
||||
* Java 用户: 使用 [sequenceOf] (`SequenceKt.sequenceOf`) 或 [asSequence] (`SequenceKt.asSequence`)
|
||||
*/
|
||||
open fun generatePreview(forward: ForwardMessage): Sequence<String> =
|
||||
forward.nodeList.asSequence().map { it.senderName + ": " + it.message.contentToString() }
|
||||
|
||||
/**
|
||||
* 显示在卡片底部
|
||||
*/
|
||||
open fun generateSummary(forward: ForwardMessage): String = "查看 ${forward.nodeList.size} 条转发消息"
|
||||
|
||||
companion object Default : DisplayStrategy() {
|
||||
@JvmSynthetic
|
||||
inline operator fun invoke(
|
||||
crossinline generateTitle: (forward: ForwardMessage) -> String = Default::generateTitle,
|
||||
crossinline generateBrief: (forward: ForwardMessage) -> String = Default::generateBrief,
|
||||
crossinline generateSource: (forward: ForwardMessage) -> String = Default::generateSource,
|
||||
crossinline generatePreview: (forward: ForwardMessage) -> Sequence<String> = Default::generatePreview,
|
||||
crossinline generateSummary: (forward: ForwardMessage) -> String = Default::generateSummary
|
||||
): DisplayStrategy = object : DisplayStrategy() {
|
||||
override fun generateTitle(forward: ForwardMessage): String = generateTitle(forward)
|
||||
override fun generateBrief(forward: ForwardMessage): String = generateBrief(forward)
|
||||
override fun generateSource(forward: ForwardMessage): String = generateSource(forward)
|
||||
override fun generatePreview(forward: ForwardMessage): Sequence<String> = generatePreview(forward)
|
||||
override fun generateSummary(forward: ForwardMessage): String = generateSummary(forward)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class Node(
|
||||
override val senderId: Long,
|
||||
override val time: Int,
|
||||
override val senderName: String,
|
||||
override val message: Message
|
||||
) : INode
|
||||
|
||||
interface INode {
|
||||
/**
|
||||
* 发送人 [User.id]
|
||||
*/
|
||||
val senderId: Long
|
||||
|
||||
/**
|
||||
* 时间戳秒
|
||||
*/
|
||||
val time: Int
|
||||
|
||||
/**
|
||||
* 发送人名称
|
||||
*/
|
||||
val senderName: String
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
val message: Message
|
||||
}
|
||||
|
||||
companion object Key : Message.Key<ForwardMessage> {
|
||||
override val typeName: String get() = "ForwardMessage"
|
||||
}
|
||||
|
||||
override fun toString(): String = "[mirai:forward:$messageList]"
|
||||
|
||||
|
||||
private val contentToString: String by lazy {
|
||||
messageList.joinToString("\n")
|
||||
}
|
||||
override fun toString(): String = "[mirai:forward:$nodeList]"
|
||||
private val contentToString: String by lazy { nodeList.joinToString("\n") }
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
override fun contentToString(): String = contentToString
|
||||
|
||||
override val length: Int
|
||||
get() = contentToString.length
|
||||
|
||||
override val length: Int get() = contentToString.length
|
||||
override fun get(index: Int): Char = contentToString[length]
|
||||
|
||||
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
|
||||
contentToString.subSequence(startIndex, endIndex)
|
||||
|
||||
override fun compareTo(other: String): Int = contentToString.compareTo(other)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 转换为 [ForwardMessage]
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmOverloads
|
||||
fun Iterable<ContactMessage>.toForwardMessage(displayStrategy: DisplayStrategy = DisplayStrategy): ForwardMessage {
|
||||
val iterator = this.iterator()
|
||||
if (!iterator.hasNext()) return ForwardMessage(emptyList(), displayStrategy)
|
||||
return ForwardMessage(
|
||||
this.map { ForwardMessage.Node(it.sender.id, it.time, it.senderName, it.message) }, displayStrategy)
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 [ForwardMessage]
|
||||
*/
|
||||
fun Message.toForwardMessage(
|
||||
sender: User,
|
||||
time: Int = currentTimeSeconds.toInt(),
|
||||
displayStrategy: DisplayStrategy = DisplayStrategy
|
||||
): ForwardMessage = this.toForwardMessage(sender.id, sender.nameCardOrNick, time, displayStrategy)
|
||||
|
||||
/**
|
||||
* 转换为 [ForwardMessage]
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmOverloads
|
||||
fun Message.toForwardMessage(
|
||||
senderId: Long,
|
||||
senderName: String,
|
||||
time: Int = currentTimeSeconds.toInt(),
|
||||
displayStrategy: DisplayStrategy = DisplayStrategy
|
||||
): ForwardMessage = ForwardMessage(listOf(ForwardMessage.Node(senderId, time, senderName, this)), displayStrategy)
|
||||
|
||||
/**
|
||||
* 构造一条 [ForwardMessage]
|
||||
*
|
||||
* @see ForwardMessageBuilder 查看 DSL 帮助
|
||||
* @see ForwardMessage 查看转发消息说明
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmSynthetic
|
||||
inline fun buildForwardMessage(
|
||||
context: Contact,
|
||||
displayStrategy: DisplayStrategy = DisplayStrategy,
|
||||
block: ForwardMessageBuilder.() -> Unit
|
||||
): ForwardMessage = ForwardMessageBuilder(context).apply { this.displayStrategy = displayStrategy }.apply(block).build()
|
||||
|
||||
/**
|
||||
* 使用 DSL 构建一个 [ForwardMessage].
|
||||
*
|
||||
* @see ForwardMessageBuilder 查看 DSL 帮助
|
||||
* @see ForwardMessage 查看转发消息说明
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmSynthetic
|
||||
inline fun ContactMessage.buildForwardMessage(
|
||||
context: Contact = this.subject,
|
||||
displayStrategy: DisplayStrategy = DisplayStrategy,
|
||||
block: ForwardMessageBuilder.() -> Unit
|
||||
): ForwardMessage = ForwardMessageBuilder(context).apply {
|
||||
this.displayStrategy = displayStrategy
|
||||
}.apply(block).build()
|
||||
|
||||
/**
|
||||
* 标记转发消息 DSL
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
|
||||
@DslMarker
|
||||
annotation class ForwardMessageDsl
|
||||
|
||||
/**
|
||||
* 转发消息 DSL 构建器.
|
||||
*
|
||||
* # 总览
|
||||
*
|
||||
* 使用 DSL 构造一个转发:
|
||||
* ```
|
||||
* buildForwardMessage {
|
||||
* 123456789 named "鸽子 A" says "咕" // 意为 名为 "鸽子 A" 的用户 123456789 发送了一条内容为 "咕" 的消息
|
||||
* 100200300 named "鸽子 C" at 1582315452 says "咕咕咕" // at 设置时间 (在 PC 端显示, 在手机端不影响顺序)
|
||||
* 987654321 named "鸽子 B" says "咕" // 未指定时间, 则自动顺序安排时间
|
||||
* 5555555 says "咕" // 未指定发送人
|
||||
* bot says { // 构造消息链, 同 `buildMessageChain`
|
||||
* +"发个图片试试"
|
||||
* +Image("{90CCED1C-2D64-313B-5D66-46625CAB31D7}.jpg")
|
||||
* }
|
||||
* val member: Member = ...
|
||||
* member says "我是幸运群员" // 使用 `User says` 则会同时设置发送人名称
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* # 语法
|
||||
*
|
||||
* 下文中 `S` 代表消息发送人. 可接受: 发送人账号 id([Long] 或 [Int]) 或 [User]
|
||||
* 下文中 `M` 代表消息内容. 可接受: [String], [Message], 或 [构造消息链][MessageChainBuilder] 的 DSL 代码块
|
||||
*
|
||||
* ## 陈述一条消息
|
||||
* 使用 [`infix fun S.says(M)`][ForwardMessageBuilder.says]
|
||||
*
|
||||
* 语句 `123456789 named "鸽子 A" says "咕"` 创建并添加了一条名为 "鸽子 A" 的用户 123456789 发送的内容为 "咕" 的消息
|
||||
*
|
||||
*
|
||||
* ### 陈述
|
||||
* 一条 '陈述' 必须包含以下属性:
|
||||
* - 发送人. 只可以作为 infix 函数的接收者 (receiver) 设置, 如 `sender says M`, `sender named "xxx"`, `sender at 123`
|
||||
* - 消息内容. 只可以通过 `says` 函数的参数设置, 即 `says M`.
|
||||
*
|
||||
* ### 组合陈述
|
||||
* 现支持的可选属性为 `named`, `at`
|
||||
*
|
||||
*
|
||||
* 最基础的陈述为 `S says M`. 可在 `says` 前按任意顺序添加组合属性:
|
||||
*
|
||||
* `S named "xxx" says M`;
|
||||
*
|
||||
* `S at 123456 says M`; 其中 `123456` 为发信时间
|
||||
*
|
||||
*
|
||||
* 属性的顺序并不重要. 如下两句陈述效果相同.
|
||||
*
|
||||
* `S named "xxx" at 123456 says M`;
|
||||
*
|
||||
* `S at 123456 named "xxx" says M`;
|
||||
*
|
||||
* ### 重复属性
|
||||
* 若属性有重复, **新属性会替换旧属性**.
|
||||
*
|
||||
* `S named "name1" named "name2" says M` 最终的发送人名称为 `"name2"`
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
class ForwardMessageBuilder private constructor(
|
||||
/**
|
||||
* 消息语境. 可为 [Group] 或 [User]
|
||||
*/
|
||||
val context: Contact,
|
||||
private val container: MutableList<ForwardMessage.INode>
|
||||
) : MutableList<ForwardMessage.INode> by container {
|
||||
/**
|
||||
* @see ForwardMessage.displayStrategy
|
||||
*/
|
||||
var displayStrategy: DisplayStrategy = DisplayStrategy
|
||||
|
||||
private var built: Boolean = false
|
||||
private fun checkBuilt() {
|
||||
check(!built) { "ForwardMessageBuilder is already built therefore can't be modified" }
|
||||
}
|
||||
|
||||
constructor(context: Contact) : this(context, mutableListOf())
|
||||
constructor(context: Contact, initialSize: Int) : this(context, ArrayList<ForwardMessage.INode>(initialSize))
|
||||
|
||||
/**
|
||||
* 当前时间.
|
||||
* 在使用 [says] 时若不指定之间, 则会使用 [currentTime] 自增 1 的事件.
|
||||
*/
|
||||
var currentTime: Int = currentTimeSeconds.toInt()
|
||||
|
||||
inner class BuilderNode : ForwardMessage.INode {
|
||||
|
||||
/**
|
||||
* 发送人 [User.id]
|
||||
*/
|
||||
override var senderId: Long = 0
|
||||
|
||||
/**
|
||||
* 时间戳秒
|
||||
*/
|
||||
override var time: Int = currentTime++
|
||||
|
||||
/**
|
||||
* 发送人名称
|
||||
*/
|
||||
override var senderName: String = ""
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
override lateinit var message: Message
|
||||
|
||||
|
||||
/**
|
||||
* 指定发送人 id 和名称.
|
||||
*/
|
||||
@ForwardMessageDsl
|
||||
infix fun sender(user: User): BuilderNode = apply { this.senderId(user.id); this.named(user.nameCardOrNick) }
|
||||
|
||||
/**
|
||||
* 指定发送人 id.
|
||||
*/
|
||||
@ForwardMessageDsl
|
||||
infix fun senderId(id: Int): BuilderNode = apply { this.senderId = id.toLongUnsigned() }
|
||||
|
||||
/**
|
||||
* 指定发送人 id.
|
||||
*/
|
||||
@ForwardMessageDsl
|
||||
infix fun senderId(id: Long): BuilderNode = apply { this.senderId = id }
|
||||
|
||||
/**
|
||||
* 指定发送人名称.
|
||||
*/
|
||||
@ForwardMessageDsl
|
||||
infix fun named(name: String): BuilderNode = apply { this.senderName = name }
|
||||
|
||||
/**
|
||||
* 指定发送人名称.
|
||||
*/
|
||||
@ForwardMessageDsl
|
||||
infix fun senderName(name: String): BuilderNode = apply { this.senderName = name }
|
||||
|
||||
/**
|
||||
* 指定时间.
|
||||
* @time 时间戳, 单位为秒
|
||||
*/
|
||||
@ForwardMessageDsl
|
||||
infix fun at(time: Int): BuilderNode = this.apply { this.time = time }
|
||||
|
||||
/**
|
||||
* 指定时间.
|
||||
* @time 时间戳, 单位为秒
|
||||
*/
|
||||
@ForwardMessageDsl
|
||||
infix fun time(time: Int): BuilderNode = this.apply { this.time = time }
|
||||
|
||||
/**
|
||||
* 指定消息内容
|
||||
*/
|
||||
@ForwardMessageDsl
|
||||
infix fun message(message: Message): BuilderNode = this.apply { this.message = message }
|
||||
|
||||
/**
|
||||
* 指定消息内容
|
||||
*/
|
||||
@ForwardMessageDsl
|
||||
infix fun message(message: String): BuilderNode = this.apply { this.message = message.toMessage() }
|
||||
|
||||
/** 添加一条消息 */
|
||||
@ForwardMessageDsl
|
||||
infix fun says(message: Message): ForwardMessageBuilder = this@ForwardMessageBuilder.apply {
|
||||
checkBuilt()
|
||||
this@BuilderNode.message = message
|
||||
add(this@BuilderNode)
|
||||
}
|
||||
|
||||
/** 添加一条消息 */
|
||||
@ForwardMessageDsl
|
||||
infix fun says(message: String): ForwardMessageBuilder = this.says(message.toMessage())
|
||||
|
||||
/** 构造并添加一个 [MessageChain] */
|
||||
@ForwardMessageDsl
|
||||
inline infix fun says(chain: @ForwardMessageDsl MessageChainBuilder.() -> Unit): ForwardMessageBuilder =
|
||||
says(MessageChainBuilder().apply(chain).asMessageChain())
|
||||
}
|
||||
|
||||
// region general `says`
|
||||
|
||||
/** 添加一条消息, 自动按顺序调整时间 */
|
||||
@ForwardMessageDsl
|
||||
infix fun Long.says(message: String): ForwardMessageBuilder = says(message.toMessage())
|
||||
|
||||
/** 添加一条消息, 自动按顺序调整时间 */
|
||||
@ForwardMessageDsl
|
||||
infix fun Int.says(message: String): ForwardMessageBuilder =
|
||||
this.toLong().and(0xFFFF_FFFF).says(message.toMessage())
|
||||
|
||||
/** 添加一条消息, 自动按顺序调整时间 */
|
||||
@ForwardMessageDsl
|
||||
infix fun Long.says(message: Message): ForwardMessageBuilder =
|
||||
this@ForwardMessageBuilder.apply {
|
||||
checkBuilt()
|
||||
add(BuilderNode().apply {
|
||||
senderId = this@says
|
||||
this.message = message
|
||||
})
|
||||
}
|
||||
|
||||
/** 添加一条消息, 自动按顺序调整时间 */
|
||||
@ForwardMessageDsl
|
||||
infix fun Int.says(message: Message): ForwardMessageBuilder = this.toLong().and(0xFFFF_FFFF).says(message)
|
||||
|
||||
/** 构造并添加一个 [MessageChain], 自动按顺序调整时间 */
|
||||
@ForwardMessageDsl
|
||||
inline infix fun Long.says(chain: @ForwardMessageDsl MessageChainBuilder.() -> Unit): ForwardMessageBuilder =
|
||||
says(MessageChainBuilder().apply(chain).asMessageChain())
|
||||
|
||||
/** 添加一条消息, 自动按顺序调整时间 */
|
||||
@ForwardMessageDsl
|
||||
inline infix fun Int.says(chain: @ForwardMessageDsl MessageChainBuilder.() -> Unit): ForwardMessageBuilder =
|
||||
this.toLong().and(0xFFFF_FFFF).says(chain)
|
||||
|
||||
|
||||
/** 添加一条消息, 自动按顺序调整时间 */
|
||||
@ForwardMessageDsl
|
||||
infix fun Bot.says(message: String): ForwardMessageBuilder = this.id named this.smartName() says message
|
||||
|
||||
/** 添加一条消息, 自动按顺序调整时间 */
|
||||
@ForwardMessageDsl
|
||||
infix fun User.says(message: String): ForwardMessageBuilder = this.id named this.nameCardOrNick says message
|
||||
|
||||
/** 添加一条消息, 自动按顺序调整时间 */
|
||||
@ForwardMessageDsl
|
||||
infix fun User.says(message: Message): ForwardMessageBuilder = this.id named this.nameCardOrNick says message
|
||||
|
||||
/** 添加一条消息, 自动按顺序调整时间 */
|
||||
@ForwardMessageDsl
|
||||
infix fun Bot.says(message: Message): ForwardMessageBuilder = this.id named this.smartName() says message
|
||||
|
||||
/** 构造并添加一个 [MessageChain], 自动按顺序调整时间 */
|
||||
@ForwardMessageDsl
|
||||
inline infix fun User.says(chain: @ForwardMessageDsl MessageChainBuilder.() -> Unit): ForwardMessageBuilder =
|
||||
this says (MessageChainBuilder().apply(chain).asMessageChain())
|
||||
|
||||
/** 构造并添加一个 [MessageChain], 自动按顺序调整时间 */
|
||||
@ForwardMessageDsl
|
||||
inline infix fun Bot.says(chain: @ForwardMessageDsl MessageChainBuilder.() -> Unit): ForwardMessageBuilder =
|
||||
this says (MessageChainBuilder().apply(chain).asMessageChain())
|
||||
|
||||
// endregion
|
||||
|
||||
|
||||
// region timed
|
||||
|
||||
/**
|
||||
* 为一条消息指定时间.
|
||||
* @time 时间戳, 单位为秒
|
||||
*/
|
||||
@ForwardMessageDsl
|
||||
infix fun Int.at(time: Int): BuilderNode = this.toLongUnsigned() at time
|
||||
|
||||
/**
|
||||
* 为一条消息指定时间.
|
||||
* @time 时间戳, 单位为秒
|
||||
*/
|
||||
@ForwardMessageDsl
|
||||
infix fun Long.at(time: Int): BuilderNode = BuilderNode().apply { senderId = this@at;this.time = time }
|
||||
|
||||
/**
|
||||
* 为一条消息指定时间和发送人名称.
|
||||
* @time 时间戳, 单位为秒
|
||||
*/
|
||||
@ForwardMessageDsl
|
||||
infix fun User.at(time: Int): BuilderNode = this.id named this.nameCardOrNick at time
|
||||
|
||||
// endregion
|
||||
|
||||
|
||||
// region named
|
||||
|
||||
/** 为一条消息指定发送人名称. */
|
||||
@ForwardMessageDsl
|
||||
infix fun Int.named(name: String): BuilderNode = this.toLongUnsigned().named(name)
|
||||
|
||||
/** 为一条消息指定发送人名称. */
|
||||
@ForwardMessageDsl
|
||||
infix fun Long.named(name: String): BuilderNode =
|
||||
BuilderNode().apply { senderId = this@named;this.senderName = name }
|
||||
|
||||
/** 为一条消息指定发送人名称. */
|
||||
@ForwardMessageDsl
|
||||
infix fun User.named(name: String): BuilderNode = this.id.named(name)
|
||||
|
||||
// endregion
|
||||
|
||||
/** 构造 [ForwardMessage] */
|
||||
fun build(): ForwardMessage = ForwardMessage(container.toList(), this.displayStrategy)
|
||||
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun Int.toLongUnsigned(): Long = this.toLong().and(0xFFFF_FFFF)
|
||||
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
internal fun Bot.smartName(): String = when (val c = this@ForwardMessageBuilder.context) {
|
||||
is Group -> c.botAsMember.nameCardOrNick
|
||||
else -> nick
|
||||
}
|
||||
}
|
@ -358,6 +358,7 @@ interface ConstrainSingle<out M : Message> : MessageMetadata {
|
||||
* @see Image 图片
|
||||
* @see RichMessage 富文本
|
||||
* @see Face 原生表情
|
||||
* @see ForwardMessage 合并转发
|
||||
*/
|
||||
interface MessageContent : SingleMessage
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
@ -151,6 +152,8 @@ constructor(serviceId: Int = 60, content: String) : ServiceMessage(serviceId, co
|
||||
|
||||
/**
|
||||
* 长消息.
|
||||
*
|
||||
* 不需要手动区分长消息和普通消息, 在 [Contact.sendMessage] 时会自动判断.
|
||||
*/
|
||||
@SinceMirai("0.31.0")
|
||||
@MiraiExperimentalAPI
|
||||
@ -164,8 +167,8 @@ class LongMessage internal constructor(content: String, val resId: String) : Ser
|
||||
* 合并转发消息
|
||||
* @suppress 此 API 非常不稳定
|
||||
*/
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
@SinceMirai("0.39.0")
|
||||
@MiraiExperimentalAPI("此 API 非常不稳定")
|
||||
internal class ForwardMessageInternal(content: String) : ServiceMessage(35, content)
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user