diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt index 127d9917e..b6e5c771c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt @@ -26,13 +26,15 @@ import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** - * 发送消息后得到的回执. 可用于撤回. - * - * 此对象持有 [Contact] 的弱引用, [Bot] 离线后将会释放引用, 届时 [target] 将无法访问. + * 发送消息后得到的回执. 可用于撤回, 引用回复等. * * @param source 指代发送出去的消息 * @param target 消息发送对象 * + * @see quote 引用这条消息. 即引用机器人自己发出去的消息 + * @see quoteReply 引用并回复这条消息. + * @see recall 撤回这条消息 + * * @see Group.sendMessage 发送群消息, 返回回执(此对象) * @see User.sendMessage 发送群消息, 返回回执(此对象) * @see Member.sendMessage 发送临时消息, 返回回执(此对象) @@ -148,6 +150,16 @@ suspend inline fun MessageReceipt.quoteReply(message: String): inline val MessageReceipt<*>.sourceId: Int get() = this.source.id + +/** + * 获取源消息 [MessageSource.internalId] + * + * @see MessageSource.id + */ +@get:JvmSynthetic +inline val MessageReceipt<*>.sourceInternalId: Int + get() = this.source.internalId + /** * 获取源消息 [MessageSource.time] * diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt index 94e41e1da..04adf1fe6 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt @@ -19,7 +19,8 @@ import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message.Key import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.PlannedRemoval -import net.mamoe.mirai.utils.SinceMirai +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic @@ -29,7 +30,7 @@ import kotlin.jvm.JvmSynthetic * * [消息][Message] 分为 * - [SingleMessage]: - * - [MessageMetadata] 消息元数据, 包括: [消息来源][MessageSource], [引用回复][QuoteReply]. + * - [MessageMetadata] 消息元数据, 即消息的属性. 包括: [消息来源][MessageSource], [引用回复][QuoteReply]. * - [MessageContent] 含内容的消息, 包括: [纯文本][PlainText], [@群员][At], [@全体成员][AtAll] 等. * - [MessageChain]: 不可变消息链, 链表形式链接的多个 [SingleMessage] 实例. * @@ -57,11 +58,6 @@ import kotlin.jvm.JvmSynthetic * #### 实现规范 * 除 [MessageChain] 外, 所有 [Message] 的实现类都有伴生对象实现 [Key] 接口. * - * #### [CharSequence] 继承 - * 所有 [CharSequence] 的行为均由 [toString] 委托. - * - * 即, `appendable.append(message)` 相当于 `appendable.append(message.toString())` - * * #### 发送消息 * - 通过 [Contact] 中的成员函数: [Contact.sendMessage] * - 通过 [Message] 的扩展函数: [Message.sendTo] @@ -186,13 +182,8 @@ interface Message { // must be interface. Don't consider any changes. } operator fun plus(another: Message): MessageChain = this.followedBy(another) - - // don't remove! avoid resolution ambiguity between `CharSequence` and `Message` operator fun plus(another: SingleMessage): MessageChain = this.followedBy(another) - operator fun plus(another: String): MessageChain = this.followedBy(another.toMessage()) - - // `+ ""` will be resolved to `plus(String)` instead of `plus(CharSeq)` operator fun plus(another: CharSequence): MessageChain = this.followedBy(another.toString().toMessage()) } @@ -200,7 +191,9 @@ interface Message { // must be interface. Don't consider any changes. /** * [Message.contentToString] 的捷径 */ -inline val Message.content: String get() = contentToString() +@get:JvmSynthetic +inline val Message.content: String + get() = contentToString() /** @@ -212,24 +205,48 @@ inline val Message.content: String get() = contentToString() * - [PlainText] 长度为 0 * - [MessageChain] 所有元素都满足 [isContentEmpty] */ -fun Message.isContentEmpty(): Boolean = when (this) { - is MessageMetadata -> true - is PlainText -> this.content.isEmpty() - is MessageChain -> this.all { it.isContentEmpty() } - else -> false -} -inline fun Message.isContentNotEmpty(): Boolean = !this.isContentEmpty() - -inline fun Message.isPlain(): Boolean = this is PlainText - -inline fun Message.isNotPlain(): Boolean = this !is PlainText - -@JvmSynthetic -@Suppress("UNCHECKED_CAST") -suspend inline fun Message.sendTo(contact: C): MessageReceipt { - return contact.sendMessage(this) as MessageReceipt +@OptIn(ExperimentalContracts::class) +fun Message.isContentEmpty(): Boolean { + contract { + returns(false) implies (this@isContentEmpty is MessageContent) + } + return when (this) { + is MessageMetadata -> true + is PlainText -> this.content.isEmpty() + is MessageChain -> this.all { it.isContentEmpty() } + else -> false + } } +@OptIn(ExperimentalContracts::class) +inline fun Message.isContentNotEmpty(): Boolean { + contract { + returns(true) implies (this@isContentNotEmpty is MessageContent) + } + return !this.isContentEmpty() +} + +@OptIn(ExperimentalContracts::class) +inline fun Message.isPlain(): Boolean { + contract { + returns(true) implies (this@isPlain is PlainText) + returns(false) implies (this@isPlain !is PlainText) + } + return this is PlainText +} + +@OptIn(ExperimentalContracts::class) +inline fun Message.isNotPlain(): Boolean { + contract { + returns(false) implies (this@isNotPlain is PlainText) + returns(true) implies (this@isNotPlain !is PlainText) + } + return this !is PlainText +} + +/** + * 将此消息元素按顺序重复 [count] 次. + */ // inline: for future removal inline fun Message.repeat(count: Int): MessageChain { if (this is ConstrainSingle<*>) { @@ -241,26 +258,28 @@ inline fun Message.repeat(count: Int): MessageChain { } } +/** + * 将此消息元素按顺序重复 [count] 次. + */ @JvmSynthetic inline operator fun Message.times(count: Int): MessageChain = this.repeat(count) -@Suppress("OverridingDeprecatedMember") +/** + * 单个消息元素. 与之相对的是 [MessageChain], 是多个 [SingleMessage] 的集合. + */ interface SingleMessage : Message { @PlannedRemoval("1.2.0") @JvmSynthetic - @SinceMirai("1.0.0") @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) fun length(): Int = this.toString().length @PlannedRemoval("1.2.0") @JvmSynthetic - @SinceMirai("1.0.0") @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) fun charAt(index: Int): Char = this.toString()[index] @PlannedRemoval("1.2.0") @JvmSynthetic - @SinceMirai("1.0.0") @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) fun subSequence(start: Int, end: Int): CharSequence = this.toString().subSequence(start, end) } @@ -268,6 +287,8 @@ interface SingleMessage : Message { /** * 消息元数据, 即不含内容的元素. * + * 这种类型的 [Message] 只表示一条消息的属性. 其子类为 [MessageSource], [QuoteReply] + * * 所有子类的 [contentToString] 都应该返回空字符串. * * @see MessageSource 消息源 @@ -284,6 +305,10 @@ interface MessageMetadata : SingleMessage * 实现此接口的元素将会在连接时自动处理替换. */ interface ConstrainSingle : MessageMetadata { + /** + * 用于判断是否为同一种元素的 [Key] + * @see Key 查看更多信息 + */ val key: Key } @@ -296,8 +321,10 @@ interface ConstrainSingle : MessageMetadata { * @see HummerMessage 一些特殊消息: [戳一戳][PokeMessage], [闪照][FlashImage] * @see Image 图片 * @see RichMessage 富文本 + * @see ServiceMessage 服务消息, 如 JSON/XML * @see Face 原生表情 * @see ForwardMessage 合并转发 + * @see Voice 语音 */ interface MessageContent : SingleMessage @@ -307,4 +334,9 @@ interface MessageContent : SingleMessage @JvmSynthetic @Suppress("UNCHECKED_CAST") suspend inline fun MessageChain.sendTo(contact: C): MessageReceipt = - contact.sendMessage(this) as MessageReceipt \ No newline at end of file + contact.sendMessage(this) as MessageReceipt + +@JvmSynthetic +@Suppress("UNCHECKED_CAST") +suspend inline fun Message.sendTo(contact: C): MessageReceipt = + contact.sendMessage(this) as MessageReceipt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt index 72ea13096..cbf71a866 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt @@ -28,13 +28,26 @@ import kotlin.reflect.KProperty /** * 消息链. 空的实现为 [EmptyMessageChain] * - * 要获取更多信息, 请查看 [Message] + * 在发送消息时必须构造一个消息链, 可通过一系列扩展函数 [asMessageChain] 转换. + * + * 要获取更多消息相关的信息, 查看 [Message] + * + * ### 构造消息链 + * + * ### 消息链如何工作 + * - [SingleMessageChainImpl] 将 [单个消息][SingleMessage] 委托为一个 [MessageChain] + * + * @see get 获取消息链中一个类型的元素, 不存在时返回 `null` + * @see getOrFail 获取消息链中一个类型的元素, 不存在时抛出异常 [NoSuchElementException] + * @see quote 引用这条消息. * * @see buildMessageChain 构造一个 [MessageChain] * @see asMessageChain 将单个 [Message] 转换为 [MessageChain] * @see asMessageChain 将 [Iterable] 或 [Sequence] 委托为 [MessageChain] * * @see forEachContent 遍历内容 + * @see allContent 判断是否每一个 [MessageContent] 都满足条件 + * @see noneContent 判断是否每一个 [MessageContent] 都不满足条件 * * @see orNull 属性委托扩展 * @see orElse 属性委托扩展 @@ -45,7 +58,7 @@ interface MessageChain : Message, Iterable { /** * 元素数量. [EmptyMessageChain] 不参加计数. */ - val size: Int + override val size: Int /** * 获取第一个类型为 [key] 的 [Message] 实例. 若不存在此实例, 返回 `null` @@ -161,7 +174,7 @@ inline fun MessageChain.firstIsInstanceOrNull(): M? = thi inline fun MessageChain.firstIsInstance(): M = this.first { it is M } as M /** - * 获取第一个 [M] 类型的 [Message] 实例 + * 判断 [this] 中是否存在 [Message] 的实例 */ @JvmSynthetic inline fun MessageChain.anyIsInstance(): Boolean = this.any { it is M } @@ -257,7 +270,7 @@ inline fun MessageChain.orElse( // endregion delegate -// region converters +// region asMessageChain /** * 得到包含 [this] 的 [MessageChain]. * @@ -409,7 +422,7 @@ inline fun MessageChain.flatten(): Sequence = this.asSequence() / /** * 不含任何元素的 [MessageChain]. */ -object EmptyMessageChain : MessageChain, Iterator { +object EmptyMessageChain : MessageChain, Iterator, List by emptyList() { override val size: Int get() = 0 override fun toString(): String = "" diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt index 65a06ee85..29bd4257b 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt @@ -79,16 +79,15 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle { - override val typeName: String - get() = "OfflineMessageSource" + override val typeName: String get() = "OfflineMessageSource" } enum class Kind { @@ -303,8 +302,6 @@ abstract class OfflineMessageSource : MessageSource() { * 消息种类 */ abstract val kind: Kind - - // final override fun toString(): String = "OfflineMessageSource(sender=$senderId, target=$targetId)" } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt index ae3e6147f..66a7c0094 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt @@ -29,6 +29,9 @@ import kotlin.jvm.JvmSynthetic * * 支持引用任何一条消息发送给任何人. * + * #### 元数据 + * [QuoteReply] 被作为 [MessageMetadata], 因为它不包含实际的消息内容, 且只能在消息中单独存在. + * * #### [source] 的类型: * - 在发送引用回复时, [source] 类型为 [OnlineMessageSource] 或 [OfflineMessageSource] * - 在接收引用回复时, [source] 类型一定为 [OfflineMessageSource] diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/JvmMethodEvents.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/JvmMethodEvents.kt new file mode 100644 index 000000000..6b9edad0e --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/JvmMethodEvents.kt @@ -0,0 +1,26 @@ +/* + * 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.event + +/** + * + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class EventHandler( + val priority: Listener.EventPriority = Listener.EventPriority.NORMAL, + val ignoreCancelled: Boolean = true +) + +interface ListenerHoster + +fun ListenerHoster.registerEvents() { + +} \ No newline at end of file