mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-09 03:20:11 +08:00
Support CustomMessage
This commit is contained in:
parent
1258746ec0
commit
96610b30e3
@ -34,6 +34,7 @@ private val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持
|
|||||||
private val UNSUPPORTED_POKE_MESSAGE_PLAIN = PlainText("[戳一戳]请使用最新版手机QQ体验新功能。")
|
private val UNSUPPORTED_POKE_MESSAGE_PLAIN = PlainText("[戳一戳]请使用最新版手机QQ体验新功能。")
|
||||||
private val UNSUPPORTED_FLASH_MESSAGE_PLAIN = PlainText("[闪照]请使用新版手机QQ查看闪照。")
|
private val UNSUPPORTED_FLASH_MESSAGE_PLAIN = PlainText("[闪照]请使用新版手机QQ查看闪照。")
|
||||||
|
|
||||||
|
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||||
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
|
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
|
||||||
internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: Boolean): MutableList<ImMsgBody.Elem> {
|
internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: Boolean): MutableList<ImMsgBody.Elem> {
|
||||||
val elements = mutableListOf<ImMsgBody.Elem>()
|
val elements = mutableListOf<ImMsgBody.Elem>()
|
||||||
@ -87,6 +88,15 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
|
|||||||
|
|
||||||
when (it) {
|
when (it) {
|
||||||
is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
|
is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
|
||||||
|
is CustomMessage -> {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
elements.add(
|
||||||
|
ImMsgBody.Elem(customElem = ImMsgBody.CustomElem(
|
||||||
|
enumType = MIRAI_CUSTOM_ELEM_TYPE,
|
||||||
|
data = CustomMessage.serialize(it.getFactory() as CustomMessage.Factory<CustomMessage>, it)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
is At -> {
|
is At -> {
|
||||||
elements.add(ImMsgBody.Elem(text = it.toJceData()))
|
elements.add(ImMsgBody.Elem(text = it.toJceData()))
|
||||||
elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
|
elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
|
||||||
@ -243,20 +253,23 @@ internal inline fun <reified R> Iterable<*>.firstIsInstanceOrNull(): R? {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val MIRAI_CUSTOM_ELEM_TYPE = "mirai".hashCode() // 103904510
|
||||||
|
|
||||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||||
@OptIn(MiraiInternalAPI::class, LowLevelAPI::class)
|
@OptIn(MiraiInternalAPI::class, LowLevelAPI::class, ExperimentalStdlibApi::class)
|
||||||
internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: Bot, message: MessageChainBuilder) {
|
internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: Bot, list: MessageChainBuilder) {
|
||||||
// (this._miraiContentToString())
|
// (this._miraiContentToString())
|
||||||
this.forEach { element ->
|
this.forEach { element ->
|
||||||
when {
|
when {
|
||||||
element.srcMsg != null ->
|
element.srcMsg != null -> {
|
||||||
message.add(QuoteReply(OfflineMessageSourceImplBySourceMsg(element.srcMsg, bot, groupIdOrZero)))
|
list.add(QuoteReply(OfflineMessageSourceImplBySourceMsg(element.srcMsg, bot, groupIdOrZero)))
|
||||||
element.notOnlineImage != null -> message.add(OnlineFriendImageImpl(element.notOnlineImage))
|
}
|
||||||
element.customFace != null -> message.add(OnlineGroupImageImpl(element.customFace))
|
element.notOnlineImage != null -> list.add(OnlineFriendImageImpl(element.notOnlineImage))
|
||||||
element.face != null -> message.add(Face(element.face.index))
|
element.customFace != null -> list.add(OnlineGroupImageImpl(element.customFace))
|
||||||
|
element.face != null -> list.add(Face(element.face.index))
|
||||||
element.text != null -> {
|
element.text != null -> {
|
||||||
if (element.text.attr6Buf.isEmpty()) {
|
if (element.text.attr6Buf.isEmpty()) {
|
||||||
message.add(element.text.str.toMessage())
|
list.add(element.text.str.toMessage())
|
||||||
} else {
|
} else {
|
||||||
val id: Long
|
val id: Long
|
||||||
element.text.attr6Buf.read {
|
element.text.attr6Buf.read {
|
||||||
@ -264,9 +277,9 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
|
|||||||
id = readUInt().toLong()
|
id = readUInt().toLong()
|
||||||
}
|
}
|
||||||
if (id == 0L) {
|
if (id == 0L) {
|
||||||
message.add(AtAll)
|
list.add(AtAll)
|
||||||
} else {
|
} else {
|
||||||
message.add(At._lowLevelConstructAtInstance(id, element.text.str))
|
list.add(At._lowLevelConstructAtInstance(id, element.text.str))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -279,7 +292,7 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
|
|||||||
else -> error("unknown compression flag=${element.lightApp.data[0]}")
|
else -> error("unknown compression flag=${element.lightApp.data[0]}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
message.add(LightApp(content))
|
list.add(LightApp(content))
|
||||||
}
|
}
|
||||||
element.richMsg != null -> {
|
element.richMsg != null -> {
|
||||||
val content = runWithBugReport("解析 richMsg", { element.richMsg.template1.toUHexString() }) {
|
val content = runWithBugReport("解析 richMsg", { element.richMsg.template1.toUHexString() }) {
|
||||||
@ -301,7 +314,7 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
|
|||||||
/**
|
/**
|
||||||
* [JsonMessage]
|
* [JsonMessage]
|
||||||
*/
|
*/
|
||||||
1 -> message.add(JsonMessage(content))
|
1 -> list.add(JsonMessage(content))
|
||||||
/**
|
/**
|
||||||
* [LongMessage], [ForwardMessage]
|
* [LongMessage], [ForwardMessage]
|
||||||
*/
|
*/
|
||||||
@ -309,17 +322,17 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
|
|||||||
val resId = this.firstIsInstanceOrNull<ImMsgBody.GeneralFlags>()?.longTextResid
|
val resId = this.firstIsInstanceOrNull<ImMsgBody.GeneralFlags>()?.longTextResid
|
||||||
|
|
||||||
if (resId != null) {
|
if (resId != null) {
|
||||||
message.add(LongMessage(content, resId))
|
list.add(LongMessage(content, resId))
|
||||||
} else {
|
} else {
|
||||||
message.add(ForwardMessage(content))
|
list.add(ForwardMessage(content))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 104 新群员入群的消息
|
// 104 新群员入群的消息
|
||||||
else -> {
|
else -> {
|
||||||
if (element.richMsg.serviceId == 60 || content.startsWith("<?")) {
|
if (element.richMsg.serviceId == 60 || content.startsWith("<?")) {
|
||||||
message.add(XmlMessage(element.richMsg.serviceId, content))
|
list.add(XmlMessage(element.richMsg.serviceId, content))
|
||||||
} else message.add(ServiceMessage(element.richMsg.serviceId, content))
|
} else list.add(ServiceMessage(element.richMsg.serviceId, content))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -328,19 +341,45 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
|
|||||||
|| element.generalFlags != null -> {
|
|| element.generalFlags != null -> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
element.customElem != null -> {
|
||||||
|
element.customElem.data.read {
|
||||||
|
kotlin.runCatching {
|
||||||
|
CustomMessage.deserialize(this)
|
||||||
|
}.fold(
|
||||||
|
onFailure = {
|
||||||
|
if (it is CustomMessage.Key.CustomMessageFullDataDeserializeInternalException) {
|
||||||
|
bot.logger.error("Internal error: " +
|
||||||
|
"exception while deserializing CustomMessage head data," +
|
||||||
|
" data=${element.customElem.data.toUHexString()}", it)
|
||||||
|
} else {
|
||||||
|
it as CustomMessage.Key.CustomMessageFullDataDeserializeUserException
|
||||||
|
bot.logger.error("User error: " +
|
||||||
|
"exception while deserializing CustomMessage body," +
|
||||||
|
" body=${it.body.toUHexString()}", it)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
onSuccess = {
|
||||||
|
if (it != null) {
|
||||||
|
list.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
element.commonElem != null -> {
|
element.commonElem != null -> {
|
||||||
when (element.commonElem.serviceType) {
|
when (element.commonElem.serviceType) {
|
||||||
2 -> {
|
2 -> {
|
||||||
val proto = element.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype2.serializer())
|
val proto = element.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype2.serializer())
|
||||||
message.add(PokeMessage(proto.pokeType, proto.vaspokeId))
|
list.add(PokeMessage(proto.pokeType, proto.vaspokeId))
|
||||||
}
|
}
|
||||||
3 -> {
|
3 -> {
|
||||||
val proto = element.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype3.serializer())
|
val proto = element.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype3.serializer())
|
||||||
if (proto.flashTroopPic != null) {
|
if (proto.flashTroopPic != null) {
|
||||||
message.add(GroupFlashImage(OnlineGroupImageImpl(proto.flashTroopPic)))
|
list.add(GroupFlashImage(OnlineGroupImageImpl(proto.flashTroopPic)))
|
||||||
}
|
}
|
||||||
if (proto.flashC2cPic != null) {
|
if (proto.flashC2cPic != null) {
|
||||||
message.add(FriendFlashImage(OnlineFriendImageImpl(proto.flashC2cPic)))
|
list.add(FriendFlashImage(OnlineFriendImageImpl(proto.flashC2cPic)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE")
|
||||||
|
|
||||||
|
package samples
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import net.mamoe.mirai.message.data.CustomMessage
|
||||||
|
import net.mamoe.mirai.message.data.CustomMessageMetadata
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义一个自定义消息类型.
|
||||||
|
* 在消息链中加入这个元素, 即可像普通元素一样发送和接收 (自动解析).
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class CustomMessageIdentifier(
|
||||||
|
val identifier1: Long,
|
||||||
|
val custom: String
|
||||||
|
) : CustomMessageMetadata() {
|
||||||
|
// 可使用 JsonSerializerFactory 或 ProtoBufSerializerFactory
|
||||||
|
companion object Factory : CustomMessage.ProtoBufSerializerFactory<CustomMessageIdentifier>(
|
||||||
|
"myMessage.CustomMessageIdentifier"
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getFactory(): Factory = Factory
|
||||||
|
}
|
@ -57,7 +57,7 @@ internal constructor(
|
|||||||
|
|
||||||
@OptIn(MiraiExperimentalAPI::class)
|
@OptIn(MiraiExperimentalAPI::class)
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return tail.toString() + left.toString()
|
return left.toString() + tail.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contentToString(): String {
|
override fun contentToString(): String {
|
||||||
|
@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* 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.message.data
|
||||||
|
|
||||||
|
import kotlinx.io.core.*
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.UnstableDefault
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
|
import kotlinx.serialization.protobuf.ProtoBuf
|
||||||
|
import kotlinx.serialization.protobuf.ProtoId
|
||||||
|
import net.mamoe.mirai.utils.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义消息
|
||||||
|
*
|
||||||
|
* 它不会显示在消息文本中, 也不会被其他客户端识别.
|
||||||
|
* 只有 mirai 才能识别这些消息.
|
||||||
|
*
|
||||||
|
* 目前在回复时无法通过 [originalMessage] 获取自定义类型消息
|
||||||
|
*
|
||||||
|
* **实现方法**:
|
||||||
|
*
|
||||||
|
* @sample samples.CustomMessageIdentifier 实现示例
|
||||||
|
*/
|
||||||
|
@SinceMirai("0.38.0")
|
||||||
|
@MiraiExperimentalAPI
|
||||||
|
sealed class CustomMessage : SingleMessage {
|
||||||
|
/**
|
||||||
|
* 获取这个消息的工厂
|
||||||
|
*/
|
||||||
|
abstract fun getFactory(): Factory<out CustomMessage>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化和反序列化此消息的工厂, 将会自动注册.
|
||||||
|
* 应实现为 `object`.
|
||||||
|
*
|
||||||
|
* @see JsonSerializerFactory 使用 [Json] 作为序列模式的 [Factory]
|
||||||
|
* @see ProtoBufSerializerFactory 使用 [ProtoBuf] 作为序列模式的 [Factory]
|
||||||
|
*/
|
||||||
|
@MiraiExperimentalAPI
|
||||||
|
abstract class Factory<M : CustomMessage>(
|
||||||
|
/**
|
||||||
|
* 此类型消息的名称.
|
||||||
|
* 在发往服务器时使用此名称.
|
||||||
|
* 应确保唯一且不变.
|
||||||
|
*/
|
||||||
|
final override val typeName: String
|
||||||
|
) : Message.Key<M> {
|
||||||
|
|
||||||
|
init {
|
||||||
|
@Suppress("LeakingThis")
|
||||||
|
register(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化此消息.
|
||||||
|
*/
|
||||||
|
@Throws(Exception::class)
|
||||||
|
abstract fun serialize(message: @UnsafeVariance M): ByteArray
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 [input] 读取此消息.
|
||||||
|
*/
|
||||||
|
@Throws(Exception::class)
|
||||||
|
abstract fun deserialize(input: ByteArray): @UnsafeVariance M
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 [ProtoBuf] 作为序列模式的 [Factory].
|
||||||
|
* 推荐使用此工厂
|
||||||
|
*/
|
||||||
|
abstract class ProtoBufSerializerFactory<M : CustomMessage>(typeName: String) :
|
||||||
|
Factory<M>(typeName) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 得到 [M] 的 [KSerializer].
|
||||||
|
*/
|
||||||
|
abstract fun serializer(): KSerializer<M>
|
||||||
|
|
||||||
|
override fun serialize(message: M): ByteArray = ProtoBuf.dump(serializer(), message)
|
||||||
|
override fun deserialize(input: ByteArray): M = ProtoBuf.load(serializer(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 [Json] 作为序列模式的 [Factory]
|
||||||
|
* 推荐在调试时使用此工厂
|
||||||
|
*/
|
||||||
|
abstract class JsonSerializerFactory<M : CustomMessage>(typeName: String) :
|
||||||
|
Factory<M>(typeName) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 得到 [M] 的 [KSerializer].
|
||||||
|
*/
|
||||||
|
abstract fun serializer(): KSerializer<M>
|
||||||
|
|
||||||
|
@OptIn(UnstableDefault::class)
|
||||||
|
open val json = Json(JsonConfiguration.Default)
|
||||||
|
|
||||||
|
override fun serialize(message: M): ByteArray = json.stringify(serializer(), message).toByteArray()
|
||||||
|
override fun deserialize(input: ByteArray): M = json.parse(serializer(), String(input))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object Key : Message.Key<CustomMessage> {
|
||||||
|
override val typeName: String get() = "CustomMessage"
|
||||||
|
private val factories: LockFreeLinkedList<Factory<*>> = LockFreeLinkedList()
|
||||||
|
|
||||||
|
internal fun register(factory: Factory<out CustomMessage>) {
|
||||||
|
factories.removeIf { it::class == factory::class }
|
||||||
|
val exist = factories.asSequence().firstOrNull { it.typeName == factory.typeName }
|
||||||
|
if (exist != null) {
|
||||||
|
error("CustomMessage.Factory typeName ${factory.typeName} is already registered by ${exist::class.qualifiedName}")
|
||||||
|
}
|
||||||
|
factories.addLast(factory)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class CustomMessageFullData(
|
||||||
|
@ProtoId(1) val miraiVersionFlag: Int,
|
||||||
|
@ProtoId(2) val typeName: String,
|
||||||
|
@ProtoId(3) val data: ByteArray
|
||||||
|
)
|
||||||
|
|
||||||
|
class CustomMessageFullDataDeserializeInternalException(cause: Throwable?) : RuntimeException(cause)
|
||||||
|
class CustomMessageFullDataDeserializeUserException(val body: ByteArray, cause: Throwable?) :
|
||||||
|
RuntimeException(cause)
|
||||||
|
|
||||||
|
internal fun deserialize(fullData: ByteReadPacket): CustomMessage? {
|
||||||
|
val msg = kotlin.runCatching {
|
||||||
|
val length = fullData.readInt()
|
||||||
|
if (fullData.remaining != length.toLong()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
ProtoBuf.load(CustomMessageFullData.serializer(), fullData.readBytes(length))
|
||||||
|
}.getOrElse {
|
||||||
|
throw CustomMessageFullDataDeserializeInternalException(it)
|
||||||
|
}
|
||||||
|
return kotlin.runCatching {
|
||||||
|
when (msg.miraiVersionFlag) {
|
||||||
|
1 -> factories.asSequence().firstOrNull { it.typeName == msg.typeName }?.deserialize(msg.data)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}.getOrElse {
|
||||||
|
throw CustomMessageFullDataDeserializeUserException(msg.data, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun <M : CustomMessage> serialize(factory: Factory<M>, message: M): ByteArray = buildPacket {
|
||||||
|
ProtoBuf.dump(CustomMessageFullData.serializer(), CustomMessageFullData(
|
||||||
|
miraiVersionFlag = 1,
|
||||||
|
typeName = factory.typeName,
|
||||||
|
data = factory.serialize(message)
|
||||||
|
)).let { data ->
|
||||||
|
writeInt(data.size)
|
||||||
|
writeFully(data)
|
||||||
|
}
|
||||||
|
}.readBytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义消息元数据.
|
||||||
|
*
|
||||||
|
* @see CustomMessage 查看更多信息
|
||||||
|
* @see ConstrainSingle 可实现此接口以保证消息链中只存在一个元素
|
||||||
|
*/
|
||||||
|
@SinceMirai("0.38.0")
|
||||||
|
@MiraiExperimentalAPI
|
||||||
|
abstract class CustomMessageMetadata : CustomMessage(), MessageMetadata {
|
||||||
|
companion object Key : Message.Key<CustomMessageMetadata> {
|
||||||
|
override val typeName: String get() = "CustomMessageMetadata"
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun customToString(): ByteArray = customToStringImpl(this.getFactory())
|
||||||
|
|
||||||
|
final override fun toString(): String =
|
||||||
|
"[mirai:custom:${getFactory().typeName}:${String(customToString())}]"
|
||||||
|
|
||||||
|
final override fun contentToString(): String = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(MiraiExperimentalAPI::class)
|
||||||
|
internal fun <T : CustomMessageMetadata> T.customToStringImpl(factory: CustomMessage.Factory<*>): ByteArray {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return (factory as CustomMessage.Factory<T>).serialize(this)
|
||||||
|
}
|
@ -13,7 +13,6 @@ package net.mamoe.mirai.message.data
|
|||||||
|
|
||||||
import net.mamoe.mirai.contact.Contact
|
import net.mamoe.mirai.contact.Contact
|
||||||
import net.mamoe.mirai.message.MessageReceipt
|
import net.mamoe.mirai.message.MessageReceipt
|
||||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.PlannedRemoval
|
import net.mamoe.mirai.utils.PlannedRemoval
|
||||||
import net.mamoe.mirai.utils.SinceMirai
|
import net.mamoe.mirai.utils.SinceMirai
|
||||||
@ -280,11 +279,9 @@ interface MessageMetadata : SingleMessage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 约束一个 [MessageChain] 中只存在这一种类型的元素. 新元素将会替换旧元素, 保持原顺序.
|
* 约束一个 [MessageChain] 中只存在这一种类型的元素. 新元素将会替换旧元素, 保持原顺序.
|
||||||
*
|
* 实现此接口的元素将会在连接时自动处理替换.
|
||||||
* **MiraiExperimentalAPI**: 此 API 可能在将来版本修改
|
|
||||||
*/
|
*/
|
||||||
@SinceMirai("0.34.0")
|
@SinceMirai("0.34.0")
|
||||||
@MiraiExperimentalAPI
|
|
||||||
interface ConstrainSingle<out M : Message> : MessageMetadata {
|
interface ConstrainSingle<out M : Message> : MessageMetadata {
|
||||||
val key: Message.Key<M>
|
val key: Message.Key<M>
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,21 @@ internal fun <M : Message> MessageChain.firstOrNullImpl(key: Message.Key<M>): M?
|
|||||||
FlashImage -> firstIsInstanceOrNull<FlashImage>()
|
FlashImage -> firstIsInstanceOrNull<FlashImage>()
|
||||||
GroupFlashImage -> firstIsInstanceOrNull<GroupFlashImage>()
|
GroupFlashImage -> firstIsInstanceOrNull<GroupFlashImage>()
|
||||||
FriendFlashImage -> firstIsInstanceOrNull<FriendFlashImage>()
|
FriendFlashImage -> firstIsInstanceOrNull<FriendFlashImage>()
|
||||||
else -> null
|
CustomMessage -> firstIsInstanceOrNull()
|
||||||
|
CustomMessageMetadata -> firstIsInstanceOrNull()
|
||||||
|
else -> {
|
||||||
|
this.forEach { message ->
|
||||||
|
if (message is CustomMessage) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
if (message.getFactory() == key) {
|
||||||
|
return message as? M
|
||||||
|
?: error("cannot cast ${message::class.qualifiedName}. Make sure CustomMessage.getFactory returns a factory that has a generic type which is the same as the type of your CustomMessage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
null
|
||||||
|
}
|
||||||
} as M?
|
} as M?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user