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( internal class MemberImpl(
qq: QQImpl, qq: QQImpl,
group: GroupImpl, 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 { override fun toString(): String {
return "Member($id)" return "Member($id)"
} }

View File

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

View File

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

View File

@ -9,3 +9,104 @@
package net.mamoe.mirai.event 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] * @param coroutineContext 给事件监听协程的额外的 [CoroutineContext]
* *
* @see subscribingGet 监听一个事件, 并尝试从这个事件中获取一个值.
* @see subscribingGetAsync 异步监听一个事件, 并尝试从这个事件中获取一个值.
*
* @see subscribeAlways 一直监听 * @see subscribeAlways 一直监听
* @see subscribeOnce 只监听一次 * @see subscribeOnce 只监听一次
* *

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.message package net.mamoe.mirai.message
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.Event
@ -44,25 +45,25 @@ class GroupMessage(
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息 * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([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(message: Message): MessageReceipt<Group> = reply(this.message.quote() + message)
suspend inline fun quoteReply(plain: String) = reply(this.message.quote() + plain) suspend inline fun quoteReply(plain: String): MessageReceipt<Group> = reply(this.message.quote() + plain)
@JvmName("reply2") @JvmName("reply2")
suspend inline fun String.quoteReply() = quoteReply(this) suspend inline fun String.quoteReply(): MessageReceipt<Group> = quoteReply(this)
@JvmName("reply2") @JvmName("reply2")
suspend inline fun Message.quoteReply() = quoteReply(this) suspend inline fun Message.quoteReply(): MessageReceipt<Group> = quoteReply(this)
@JvmName("reply2") @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 MessageChain.recall() = group.recall(this)
suspend inline fun MessageSource.recall() = group.recall(this) suspend inline fun MessageSource.recall() = group.recall(this)
inline fun MessageSource.recallIn(delay: Long) = group.recallIn(this, delay) inline fun MessageSource.recallIn(delay: Long): Job = group.recallIn(this, delay)
inline fun MessageChain.recallIn(delay: Long) = group.recallIn(this, delay) inline fun MessageChain.recallIn(delay: Long): Job = group.recallIn(this, delay)
override fun toString(): String = override fun toString(): String =
"GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)" "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.contact.QQ
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.events.BotEvent 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.message.data.*
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import kotlin.jvm.JvmName 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) suspend inline fun String.send() = this.toMessage().sendTo(subject)
// endregion // endregion
operator fun <M : Message> get(at: Message.Key<M>): M {
return this.message[at]
}
/** /**
* 创建 @ 这个账号的消息. 当且仅当消息为群消息时可用. 否则将会抛出 [IllegalArgumentException] * 创建 @ 这个账号的消息. 当且仅当消息为群消息时可用. 否则将会抛出 [IllegalArgumentException]
*/ */
@ -134,4 +140,47 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
*/ */
suspend inline fun Image.download(): ByteReadPacket = bot.run { download() } suspend inline fun Image.download(): ByteReadPacket = bot.run { download() }
// endregion // 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.BotAccount
import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.isOperator
import net.mamoe.mirai.contact.sendMessage import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.event.* import net.mamoe.mirai.event.*
import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.data.AtAll import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.nextMessage
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.firstOrNull
import net.mamoe.mirai.message.sendAsImageTo 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.FileBasedDeviceInfo
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import java.io.File import java.io.File
@ -49,7 +46,7 @@ private fun readTestAccount(): BotAccount? {
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
suspend fun main() { suspend fun main() {
val bot = QQAndroid.Bot( // JVM 下也可以不写 `QQAndroid.` 引用顶层函数 val bot = Bot( // JVM 下也可以不写 `QQAndroid.` 引用顶层函数
123456789, 123456789,
"123456" "123456"
) { ) {
@ -207,6 +204,25 @@ fun Bot.messageDSL() {
// sender: QQ // sender: QQ
// it: String (来自 MessageChain.toString) // it: String (来自 MessageChain.toString)
// group: Group // 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
}
} }
} }