Support ForwardMessage DSL

This commit is contained in:
Him188 2020-04-23 14:17:24 +08:00
parent 6734403fb0
commit 1b4e1475a0
8 changed files with 619 additions and 56 deletions

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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()
)
)
)

View File

@ -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
}
}

View File

@ -358,6 +358,7 @@ interface ConstrainSingle<out M : Message> : MessageMetadata {
* @see Image 图片
* @see RichMessage 富文本
* @see Face 原生表情
* @see ForwardMessage 合并转发
*/
interface MessageContent : SingleMessage

View File

@ -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)
/*