This commit is contained in:
Him188 2020-05-09 13:45:29 +08:00
parent ed11f38b3f
commit 9733490266
6 changed files with 136 additions and 53 deletions

View File

@ -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 <C : Contact> MessageReceipt<C>.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]
*

View File

@ -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 <C : Contact> Message.sendTo(contact: C): MessageReceipt<C> {
return contact.sendMessage(this) as MessageReceipt<C>
@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<out M : Message> : MessageMetadata {
/**
* 用于判断是否为同一种元素的 [Key]
* @see Key 查看更多信息
*/
val key: Key<M>
}
@ -296,8 +321,10 @@ interface ConstrainSingle<out M : Message> : 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 <C : Contact> MessageChain.sendTo(contact: C): MessageReceipt<C> =
contact.sendMessage(this) as MessageReceipt<C>
contact.sendMessage(this) as MessageReceipt<C>
@JvmSynthetic
@Suppress("UNCHECKED_CAST")
suspend inline fun <C : Contact> Message.sendTo(contact: C): MessageReceipt<C> =
contact.sendMessage(this) as MessageReceipt<C>

View File

@ -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<SingleMessage> {
/**
* 元素数量. [EmptyMessageChain] 不参加计数.
*/
val size: Int
override val size: Int
/**
* 获取第一个类型为 [key] [Message] 实例. 若不存在此实例, 返回 `null`
@ -161,7 +174,7 @@ inline fun <reified M : Message?> MessageChain.firstIsInstanceOrNull(): M? = thi
inline fun <reified M : Message> MessageChain.firstIsInstance(): M = this.first { it is M } as M
/**
* 获取第一个 [M] 类型的 [Message] 实例
* 判断 [this] 中是否存在 [Message] 实例
*/
@JvmSynthetic
inline fun <reified M : Message> MessageChain.anyIsInstance(): Boolean = this.any { it is M }
@ -257,7 +270,7 @@ inline fun <reified T : Message?> MessageChain.orElse(
// endregion delegate
// region converters
// region asMessageChain
/**
* 得到包含 [this] [MessageChain].
*
@ -409,7 +422,7 @@ inline fun MessageChain.flatten(): Sequence<SingleMessage> = this.asSequence() /
/**
* 不含任何元素的 [MessageChain].
*/
object EmptyMessageChain : MessageChain, Iterator<SingleMessage> {
object EmptyMessageChain : MessageChain, Iterator<SingleMessage>, List<SingleMessage> by emptyList() {
override val size: Int get() = 0
override fun toString(): String = ""

View File

@ -79,16 +79,15 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSo
*
* #### 顺序
* 群消息的 id 由服务器维护. 好友消息的 id mirai 维护.
* id 不一定从 0 开始.
*
* - 在同一个群的消息中此值随每条消息递增 1.
* - 在同一个群的消息中此值随每条消息递增 1, 但此行为由服务器决定, mirai 不保证自增顺序.
* - 在好友消息中无法保证每次都递增 1. 也可能会产生大幅跳过的情况.
*/
abstract val id: Int
/**
* 内部 id. 仅用于协议模块使用.
*
* 在撤回消息和引用回复时均需使用此 id.
* 内部 id. **仅用于协议模块使用**
*
* 值没有顺序, 也可能为 0, 取决于服务器是否提供.
*
@ -100,8 +99,6 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSo
* 发送时间时间戳, 单位为秒.
*
* 时间戳可能来自服务器, 也可能来自 mirai, 且无法保证两者时间同步.
*
* 撤回消息时需要此值.
*/
abstract val time: Int
@ -188,6 +185,10 @@ sealed class OnlineMessageSource : MessageSource() {
*/
abstract val subject: Contact
/*
* 以下子类型仅是覆盖了 [target], [subject], [sender] 等的类型
*/
/**
* [机器人主动发送消息][Contact.sendMessage] 产生的 [MessageSource], 可通过 [MessageReceipt] 获得.
*/
@ -229,7 +230,6 @@ sealed class OnlineMessageSource : MessageSource() {
abstract override val target: Group
final override val subject: Group get() = target
// final override fun toString(): String = "OnlineMessageSource.ToGroup(group=${target.id})"
}
}
@ -289,8 +289,7 @@ sealed class OnlineMessageSource : MessageSource() {
*/
abstract class OfflineMessageSource : MessageSource() {
companion object Key : Message.Key<OfflineMessageSource> {
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)"
}
/**

View File

@ -29,6 +29,9 @@ import kotlin.jvm.JvmSynthetic
*
* 支持引用任何一条消息发送给任何人.
*
* #### 元数据
* [QuoteReply] 被作为 [MessageMetadata], 因为它不包含实际的消息内容, 且只能在消息中单独存在.
*
* #### [source] 的类型:
* - 在发送引用回复时, [source] 类型为 [OnlineMessageSource] [OfflineMessageSource]
* - 在接收引用回复时, [source] 类型一定为 [OfflineMessageSource]

View File

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