Merge remote-tracking branch 'origin'

This commit is contained in:
ryoii 2020-02-21 22:52:10 +08:00
commit 5c287faf4e
8 changed files with 211 additions and 28 deletions

View File

@ -166,7 +166,7 @@ internal class QQImpl(
}
@Suppress("MemberVisibilityCanBePrivate", "DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
@Suppress("MemberVisibilityCanBePrivate")
internal class MemberImpl(
qq: QQImpl,
group: GroupImpl,
@ -286,6 +286,20 @@ internal class MemberImpl(
}
}
override fun hashCode(): Int {
var result = bot.hashCode()
result = 31 * result + id.hashCode()
return result
}
@Suppress("DuplicatedCode")
override fun equals(other: Any?): Boolean { // 不要删除. trust me
if (this === other) return true
if (other !is Contact) return false
if (this::class != other::class) return false
return this.id == other.id && this.bot == other.bot
}
override fun toString(): String {
return "Member($id)"
}

View File

@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
@ -19,11 +19,10 @@ import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.event.events.BotJoinGroupEvent
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.MemberJoinEvent
import net.mamoe.mirai.event.subscribe
import net.mamoe.mirai.event.subscribingGetAsync
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
@ -279,17 +278,14 @@ internal class MessageSvc {
override val groupId: Long,
override val sourceMessage: MessageChain
) : MessageSource {
lateinit var sequenceIdDeferred: CompletableDeferred<Int>
lateinit var sequenceIdDeferred: Deferred<Int>
@UseExperimental(MiraiExperimentalAPI::class)
fun startWaitingSequenceId(contact: Contact) {
sequenceIdDeferred = CompletableDeferred()
contact.subscribe<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt> { event ->
if (event.messageRandom == messageUid.toInt()) {
sequenceIdDeferred.complete(event.sequenceId)
return@subscribe ListeningStatus.STOPPED
}
return@subscribe ListeningStatus.LISTENING
sequenceIdDeferred = contact.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int> {
if (it.messageRandom == messageUid.toInt()) {
it.sequenceId
} else null
}
}

View File

@ -72,6 +72,9 @@ interface Member : QQ, Contact {
/**
* 禁言.
*
* QQ 中最小操作和显示的时间都是一分钟.
* 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
*
* 管理员可禁言成员, 群主可禁言管理员和群员.
*
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.

View File

@ -9,3 +9,104 @@
package net.mamoe.mirai.event
import kotlinx.coroutines.*
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/**
* 挂起当前协程, 监听这个事件, 并尝试从这个事件中获取一个值.
*
* [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
* @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值
*
* @see subscribingGetAsync 本函数的异步版本
*/
@MiraiExperimentalAPI
suspend inline fun <reified E : Event, R : Any> subscribingGet(
timeoutMillis: Long = -1,
noinline filter: E.(E) -> R? // 不要 crossinline: crossinline 后 stacktrace 会不正常
): R {
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" }
return subscribingGetOrNull(timeoutMillis, filter) ?: error("timeout subscribingGet")
}
/**
* 挂起当前协程, 监听这个事件, 并尝试从这个事件中获取一个值.
*
* [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
* @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值
*
* @see subscribingGetAsync 本函数的异步版本
*/
@MiraiExperimentalAPI
suspend inline fun <reified E : Event, R : Any> subscribingGetOrNull(
timeoutMillis: Long = -1,
noinline filter: E.(E) -> R? // 不要 crossinline: crossinline 后 stacktrace 会不正常
): R? {
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" }
var result: R? = null
var resultThrowable: Throwable? = null
if (timeoutMillis == -1L) {
@Suppress("DuplicatedCode") // for better performance
coroutineScope {
var listener: Listener<E>? = null
listener = this.subscribe {
val value = try {
filter.invoke(this, it)
} catch (e: Exception) {
resultThrowable = e
return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() }
}
if (value != null) {
result = value
return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() }
} else return@subscribe ListeningStatus.LISTENING
}
}
} else {
withTimeoutOrNull(timeoutMillis) {
var listener: Listener<E>? = null
@Suppress("DuplicatedCode") // for better performance
listener = this.subscribe {
val value = try {
filter.invoke(this, it)
} catch (e: Exception) {
resultThrowable = e
return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() }
}
if (value != null) {
result = value
return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() }
} else return@subscribe ListeningStatus.LISTENING
}
}
}
resultThrowable?.let { throw it }
return result
}
/**
* 异步监听这个事件, 并尝试从这个事件中获取一个值.
*
* [filter] 抛出了一个异常, [Deferred.await] 会抛出这个异常或.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
* @param coroutineContext 额外的 [CoroutineContext]
* @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值
*/
@MiraiExperimentalAPI
inline fun <reified E : Event, R : Any> CoroutineScope.subscribingGetAsync(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
timeoutMillis: Long = -1,
noinline filter: E.(E) -> R? // 不要 crossinline: crossinline 后 stacktrace 会不正常
): Deferred<R> = this.async(coroutineContext) {
subscribingGet(timeoutMillis, filter)
}

View File

@ -101,6 +101,9 @@ interface Listener<in E : Event> : CompletableJob {
*
* @param coroutineContext 给事件监听协程的额外的 [CoroutineContext]
*
* @see subscribingGet 监听一个事件, 并尝试从这个事件中获取一个值.
* @see subscribingGetAsync 异步监听一个事件, 并尝试从这个事件中获取一个值.
*
* @see subscribeAlways 一直监听
* @see subscribeOnce 只监听一次
*

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.message
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.Event
@ -44,25 +45,25 @@ class GroupMessage(
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
suspend inline fun quoteReply(message: MessageChain) = reply(this.message.quote() + message)
suspend inline fun quoteReply(message: MessageChain): MessageReceipt<Group> = reply(this.message.quote() + message)
suspend inline fun quoteReply(message: Message) = reply(this.message.quote() + message)
suspend inline fun quoteReply(plain: String) = reply(this.message.quote() + plain)
suspend inline fun quoteReply(message: Message): MessageReceipt<Group> = reply(this.message.quote() + message)
suspend inline fun quoteReply(plain: String): MessageReceipt<Group> = reply(this.message.quote() + plain)
@JvmName("reply2")
suspend inline fun String.quoteReply() = quoteReply(this)
suspend inline fun String.quoteReply(): MessageReceipt<Group> = quoteReply(this)
@JvmName("reply2")
suspend inline fun Message.quoteReply() = quoteReply(this)
suspend inline fun Message.quoteReply(): MessageReceipt<Group> = quoteReply(this)
@JvmName("reply2")
suspend inline fun MessageChain.quoteReply() = quoteReply(this)
suspend inline fun MessageChain.quoteReply(): MessageReceipt<Group> = quoteReply(this)
suspend inline fun MessageChain.recall() = group.recall(this)
suspend inline fun MessageSource.recall() = group.recall(this)
inline fun MessageSource.recallIn(delay: Long) = group.recallIn(this, delay)
inline fun MessageChain.recallIn(delay: Long) = group.recallIn(this, delay)
inline fun MessageSource.recallIn(delay: Long): Job = group.recallIn(this, delay)
inline fun MessageChain.recallIn(delay: Long): Job = group.recallIn(this, delay)
override fun toString(): String =
"GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)"

View File

@ -21,6 +21,8 @@ import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.event.subscribingGet
import net.mamoe.mirai.event.subscribingGetAsync
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.*
import kotlin.jvm.JvmName
@ -109,6 +111,10 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
suspend inline fun String.send() = this.toMessage().sendTo(subject)
// endregion
operator fun <M : Message> get(at: Message.Key<M>): M {
return this.message[at]
}
/**
* 创建 @ 这个账号的消息. 当且仅当消息为群消息时可用. 否则将会抛出 [IllegalArgumentException]
*/
@ -134,4 +140,47 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
*/
suspend inline fun Image.download(): ByteReadPacket = bot.run { download() }
// endregion
}
/**
* 判断两个 [MessagePacket] [MessagePacket.sender] [MessagePacket.subject] 是否相同
*/
fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Boolean {
return this.sender == another.sender && this.subject == another.subject
}
/**
* 挂起当前协程, 等待下一条 [MessagePacket.sender] [MessagePacket.subject] [P] 相同且通过 [筛选][filter] [MessagePacket]
*
* [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
* @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值
*
* @see subscribingGetAsync 本函数的异步版本
*/
suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage(
timeoutMillis: Long = -1,
crossinline filter: P.(P) -> Boolean
): P {
return subscribingGet<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessage) }?.takeIf { filter(it, it) }
}
}
/**
* 挂起当前协程, 等待下一条 [MessagePacket.sender] [MessagePacket.subject] [P] 相同的 [MessagePacket]
*
* [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
*
* @see subscribingGetAsync 本函数的异步版本
*/
suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage(
timeoutMillis: Long = -1
): P {
return subscribingGet<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessage) }
}
}

View File

@ -16,17 +16,14 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.isOperator
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.event.*
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.data.AtAll
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.firstOrNull
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.nextMessage
import net.mamoe.mirai.message.sendAsImageTo
import net.mamoe.mirai.qqandroid.Bot
import net.mamoe.mirai.qqandroid.QQAndroid
import net.mamoe.mirai.utils.FileBasedDeviceInfo
import net.mamoe.mirai.utils.MiraiInternalAPI
import java.io.File
@ -49,7 +46,7 @@ private fun readTestAccount(): BotAccount? {
@Suppress("UNUSED_VARIABLE")
suspend fun main() {
val bot = QQAndroid.Bot( // JVM 下也可以不写 `QQAndroid.` 引用顶层函数
val bot = Bot( // JVM 下也可以不写 `QQAndroid.` 引用顶层函数
123456789,
"123456"
) {
@ -207,6 +204,25 @@ fun Bot.messageDSL() {
// sender: QQ
// it: String (来自 MessageChain.toString)
// group: Group
case("recall") {
reply("😎").recallIn(3000) // 3 秒后自动撤回这条消息
}
case("禁言") {
// 挂起当前协程, 等待下一条满足条件的消息.
// 发送 "禁言" 后需要再发送一条消息 at 一个人.
val value: At = nextMessage { message.any(At) }[At]
value.member().mute(10)
}
startsWith("群名=") {
if (!sender.isOperator()) {
sender.mute(5)
return@startsWith
}
group.name = it
}
}
}