From 7ffbccb9aa47bcaf4c6b582dd54a8cfc05930459 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 22 Feb 2020 21:12:10 +0800 Subject: [PATCH 01/12] Misc improvements --- .../commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt | 6 ++++-- .../kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt index fb1fc8942..a00d8021d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt @@ -92,6 +92,8 @@ interface Contact : CoroutineScope { override fun toString(): String } -suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.toChain()) +@Suppress("UNCHECKED_CAST") +suspend inline fun C.sendMessage(message: Message): MessageReceipt = + sendMessage(message.toChain()) as? MessageReceipt ?: error("Internal class cast mistake") -suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.toMessage()) \ No newline at end of file +suspend inline fun C.sendMessage(plain: String): MessageReceipt = sendMessage(plain.toMessage()) \ No newline at end of file diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt index d589fed7c..ac990c210 100644 --- a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt +++ b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt @@ -31,6 +31,7 @@ import java.io.OutputStream import java.util.stream.Stream import kotlin.streams.asStream +@UseExperimental(MiraiInternalAPI::class, MiraiExperimentalAPI::class) internal class BlockingBotImpl(private val bot: Bot) : BlockingBot { @MiraiInternalAPI override fun getAccount(): BotAccount = bot.account @@ -51,7 +52,6 @@ internal class BlockingBotImpl(private val bot: Bot) : BlockingBot { override fun getFriend(id: Long): BlockingQQ = bot.getFriend(id).blocking() override fun queryGroupList(): Stream = runBlocking { bot.queryGroupList() }.asStream() - @UseExperimental(MiraiInternalAPI::class) override fun getGroupList(): List = bot.groups.delegate.toList().map { it.blocking() } override fun queryGroupInfo(code: Long): GroupInfo = runBlocking { bot.queryGroupInfo(code) } From 8454d782938249a90a08f391719dd5329aee3c02 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 22 Feb 2020 21:12:32 +0800 Subject: [PATCH 02/12] Move `recall` from `Contact` to `Group` --- .../net/mamoe/mirai/qqandroid/ContactImpl.kt | 16 +--- .../net/mamoe/mirai/qqandroid/QQAndroidBot.kt | 22 ++++- .../commonMain/kotlin/net.mamoe.mirai/Bot.kt | 81 +++++++++++++++++++ .../kotlin/net.mamoe.mirai/contact/Group.kt | 60 -------------- 4 files changed, 100 insertions(+), 79 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt index 873725f57..d2b80a21b 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt @@ -22,7 +22,6 @@ import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper import net.mamoe.mirai.qqandroid.network.highway.postImage import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopMemberInfo import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352 -import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn @@ -505,25 +504,12 @@ internal class GroupImpl( } } + @MiraiExperimentalAPI override suspend fun quit(): Boolean { check(botPermission != MemberPermission.OWNER) { "An owner cannot quit from a owning group" } TODO("not implemented") } - override suspend fun recall(source: MessageSource) { - if (source.senderId != bot.uin) { - checkBotPermissionOperator() - } - - source.ensureSequenceIdAvailable() - - bot.network.run { - val response = PbMessageSvc.PbMsgWithDraw.Group(bot.client, this@GroupImpl.id, source.sequenceId, source.messageUid.toInt()) - .sendAndExpect() - check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.sequenceId}: $response" } - } - } - @UseExperimental(MiraiExperimentalAPI::class) override fun Member(memberInfo: MemberInfo): Member { return MemberImpl( diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index 5c6c89699..7b9c2326a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -12,18 +12,17 @@ package net.mamoe.mirai.qqandroid import kotlinx.io.core.ByteReadPacket import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotImpl -import net.mamoe.mirai.contact.ContactList -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.contact.filteringGetOrNull +import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList import net.mamoe.mirai.utils.* @@ -119,6 +118,21 @@ internal abstract class QQAndroidBotBase constructor( TODO("not implemented") } + override suspend fun recall(source: MessageSource) { + if (source.senderId != uin) { + getGroup(source.groupId).checkBotPermissionOperator() + } + + source.ensureSequenceIdAvailable() + + network.run { + val response: PbMessageSvc.PbMsgWithDraw.Response = + PbMessageSvc.PbMsgWithDraw.Group(bot.client, source.groupId, source.sequenceId, source.messageUid) + .sendAndExpect() + check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.sequenceId}: $response" } + } + } + override suspend fun Image.download(): ByteReadPacket { TODO("not implemented") } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index 11b5b9460..24ea3516e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -11,8 +11,10 @@ package net.mamoe.mirai +import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import kotlinx.io.OutputStream import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.IoBuffer @@ -23,10 +25,14 @@ import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.io.transferTo +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmStatic /** @@ -208,12 +214,37 @@ abstract class Bot : CoroutineScope { // region actions + /** + * 撤回这条消息. + * + * [Bot] 撤回自己的消息不需要权限. + * [Bot] 撤回群员的消息需要管理员权限. + * + * @throws PermissionDeniedException 当 [Bot] 无权限操作时 + * @see Bot.recall (扩展函数) 接受参数 [MessageChain] + */// source.groupId, source.sequenceId, source.messageUid + abstract suspend fun recall(source: MessageSource) + + /** + * 撤回这条消息. + * + * [Bot] 撤回自己的消息不需要权限. + * [Bot] 撤回群员的消息需要管理员权限. + * + * @throws PermissionDeniedException 当 [Bot] 无权限操作时 + * @see Bot.recall (扩展函数) 接受参数 [MessageChain] + * @see 更推荐说 + */ + abstract suspend fun recall(groupId: Long, messageSequenceId: Int, messageUid: Int) + @Deprecated("内存使用效率十分低下", ReplaceWith("this.download()"), DeprecationLevel.WARNING) + @MiraiExperimentalAPI("未支持") abstract suspend fun Image.downloadAsByteArray(): ByteArray /** * 将图片下载到内存中 (使用 [IoBuffer.Pool]) */ + @MiraiExperimentalAPI("未支持") abstract suspend fun Image.download(): ByteReadPacket /** @@ -222,11 +253,13 @@ abstract class Bot : CoroutineScope { * @param message 若需要验证请求时的验证消息. * @param remark 好友备注 */ + @MiraiExperimentalAPI("未支持") abstract suspend fun addFriend(id: Long, message: String? = null, remark: String? = null): AddFriendResult /** * 同意来自陌生人的加好友请求 */ + @MiraiExperimentalAPI("未支持") abstract suspend fun approveFriendAddRequest(id: Long, remark: String?) // endregion @@ -252,12 +285,60 @@ abstract class Bot : CoroutineScope { /** * 需要调用者自行 close [output] */ + @MiraiExperimentalAPI("未支持") suspend inline fun Image.downloadTo(output: OutputStream) = download().use { input -> input.transferTo(output) } // endregion } +/** + * 撤回这条消息. + * 根据 [message] 内的 [MessageSource] 进行相关判断. + * + * [Bot] 撤回自己的消息不需要权限. + * [Bot] 撤回群员的消息需要管理员权限. + * + * @throws PermissionDeniedException 当 [Bot] 无权限操作时 + * @see Bot.recall + */ +@MiraiExperimentalAPI +suspend inline fun Bot.recall(message: MessageChain) = this.recall(message[MessageSource]) + +/** + * 在一段时间后撤回这条消息. + * 将根据 [MessageSource.groupId] 判断消息是群消息还是好友消息. + * + * @param millis 延迟的时间, 单位为毫秒 + * @param coroutineContext 额外的 [CoroutineContext] + * @see recall + */ +fun Bot.recallIn( + source: MessageSource, + millis: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext +): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) { + kotlinx.coroutines.delay(millis) + recall(source) +} + +/** + * 在一段时间后撤回这条消息. + * + * @param millis 延迟的时间, 单位为毫秒 + * @param coroutineContext 额外的 [CoroutineContext] + * @see recall + */ +@MiraiExperimentalAPI +fun Bot.recallIn( + message: MessageChain, + millis: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext +): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) { + kotlinx.coroutines.delay(millis) + recall(message) +} + /** * 关闭这个 [Bot], 停止一切相关活动. 所有引用都会被释放. * diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt index 8b1c1ed42..9b36e5cae 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt @@ -11,10 +11,7 @@ package net.mamoe.mirai.contact -import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch import net.mamoe.mirai.Bot import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.event.events.* @@ -22,10 +19,7 @@ import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.utils.MiraiExperimentalAPI -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmName /** @@ -151,17 +145,6 @@ interface Group : Contact, CoroutineScope { @MiraiExperimentalAPI("还未支持") suspend fun quit(): Boolean - /** - * 撤回这条消息. - * - * [Bot] 撤回自己的消息不需要权限. - * [Bot] 撤回群员的消息需要管理员权限. - * - * @throws PermissionDeniedException 当 [Bot] 无权限操作时 - * @see Group.recall (扩展函数) 接受参数 [MessageChain] - */ - suspend fun recall(source: MessageSource) - /** * 构造一个 [Member]. * 非特殊情况请不要使用这个函数. 优先使用 [get]. @@ -226,49 +209,6 @@ interface Group : Contact, CoroutineScope { fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})" } -/** - * 撤回这条消息. - * - * [Bot] 撤回自己的消息不需要权限. - * [Bot] 撤回群员的消息需要管理员权限. - * - * @throws PermissionDeniedException 当 [Bot] 无权限操作时 - * @see Group.recall - */ -suspend inline fun Group.recall(message: MessageChain) = this.recall(message[MessageSource]) - -/** - * 在一段时间后撤回这条消息. - * - * @param millis 延迟的时间, 单位为毫秒 - * @param coroutineContext 额外的 [CoroutineContext] - * @see recall - */ -fun Group.recallIn( - message: MessageSource, - millis: Long, - coroutineContext: CoroutineContext = EmptyCoroutineContext -): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) { - kotlinx.coroutines.delay(millis) - recall(message) -} - -/** - * 在一段时间后撤回这条消息. - * - * @param millis 延迟的时间, 单位为毫秒 - * @param coroutineContext 额外的 [CoroutineContext] - * @see recall - */ -fun Group.recallIn( - message: MessageChain, - millis: Long, - coroutineContext: CoroutineContext = EmptyCoroutineContext -): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) { - kotlinx.coroutines.delay(millis) - recall(message) -} - /** * 返回机器人是否正在被禁言 * From b70bf5e042bc6dda1332be7e8a597d767e8765d5 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 22 Feb 2020 21:47:02 +0800 Subject: [PATCH 03/12] Rewrite `recall` and `MessageSource.id` --- .../mirai/api/http/data/common/MessageDTO.kt | 2 +- .../net/mamoe/mirai/qqandroid/ContactImpl.kt | 4 +- .../net/mamoe/mirai/qqandroid/QQAndroidBot.kt | 19 ++++++++- .../qqandroid/message/MessageSourceFromMsg.kt | 18 +++++--- .../packet/chat/receive/MessageSvc.kt | 27 ++++++------ .../commonMain/kotlin/net.mamoe.mirai/Bot.kt | 6 ++- .../kotlin/net.mamoe.mirai/contact/Contact.kt | 42 +++++++++++++++++++ .../net.mamoe.mirai/message/GroupMessage.kt | 18 +++++--- .../net.mamoe.mirai/message/MessageReceipt.kt | 10 +++-- .../message/data/MessageSource.kt | 39 ++++++++++++----- 10 files changed, 141 insertions(+), 44 deletions(-) diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt index a4a30d82f..a66b42fd6 100644 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt +++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt @@ -99,7 +99,7 @@ fun MessageChain.toDTOChain() = mutableListOf(this[MessageSource].toDTO()).apply fun MessageChainDTO.toMessageChain(contact: Contact) = MessageChain().apply { this@toMessageChain.forEach { add(it.toMessage(contact)) } } -internal fun MessageSource.calMessageId() = (messageUid.toLong() shl 32) or (sequenceId.toLong() and 0xFFFFFFFF) +internal fun MessageSource.calMessageId() = (messageRandom.toLong() shl 32) or (sequenceId.toLong() and 0xFFFFFFFF) @UseExperimental(ExperimentalUnsignedTypes::class) fun Message.toDTO() = when (this) { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt index d2b80a21b..8d1c88117 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt @@ -79,7 +79,7 @@ internal class QQImpl( ) { source = it }.sendAndExpect() is MessageSvc.PbSendMsg.Response.SUCCESS ) { "send message failed" } } - return MessageReceipt(message, source, this) + return MessageReceipt(source, this) } override suspend fun uploadImage(image: ExternalImage): Image = try { @@ -553,7 +553,7 @@ internal class GroupImpl( source.startWaitingSequenceId(this) - return MessageReceipt(message, source, this) + return MessageReceipt(source, this) } override suspend fun uploadImage(image: ExternalImage): Image = try { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index 7b9c2326a..a7e2f8bd5 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -19,6 +19,8 @@ import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageSource +import net.mamoe.mirai.message.data.messageRandom +import net.mamoe.mirai.message.data.sequenceId import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl @@ -127,12 +129,27 @@ internal abstract class QQAndroidBotBase constructor( network.run { val response: PbMessageSvc.PbMsgWithDraw.Response = - PbMessageSvc.PbMsgWithDraw.Group(bot.client, source.groupId, source.sequenceId, source.messageUid) + PbMessageSvc.PbMsgWithDraw.Group(bot.client, source.groupId, source.sequenceId, source.messageRandom) .sendAndExpect() check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.sequenceId}: $response" } } } + override suspend fun recall(groupId: Long, senderId: Long, messageId: Long) { + if (senderId != uin) { + getGroup(groupId).checkBotPermissionOperator() + } + + val sequenceId = (messageId shr 32).toInt() + + network.run { + val response: PbMessageSvc.PbMsgWithDraw.Response = + PbMessageSvc.PbMsgWithDraw.Group(bot.client, groupId, sequenceId, messageId.toInt()) + .sendAndExpect() + check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #$sequenceId: $response" } + } + } + override suspend fun Image.download(): ByteReadPacket { TODO("not implemented") } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceFromMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceFromMsg.kt index 44065d95b..ac8fa65ee 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceFromMsg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceFromMsg.kt @@ -11,6 +11,7 @@ package net.mamoe.mirai.qqandroid.message import net.mamoe.mirai.contact.Group import net.mamoe.mirai.message.data.MessageSource +import net.mamoe.mirai.message.data.messageRandom import net.mamoe.mirai.qqandroid.io.serialization.loadAs import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody @@ -21,13 +22,16 @@ internal inline class MessageSourceFromServer( val delegate: ImMsgBody.SourceMsg ) : MessageSource { override val time: Long get() = delegate.time.toLong() and 0xFFFFFFFF - override val sequenceId: Int get() = delegate.origSeqs?.firstOrNull() ?: error("cannot find sequenceId from ImMsgBody.SourceMsg") + + override val id: Long + get() = (delegate.origSeqs?.firstOrNull() ?: error("cannot find sequenceId from ImMsgBody.SourceMsg")).toLong().shl(32) or + (delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.toInt()).toLong().and(0xFFFFFFFF) + override suspend fun ensureSequenceIdAvailable() { // nothing to do } - override val messageUid: Int get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.toInt() // override val sourceMessage: MessageChain get() = delegate.toMessageChain() override val senderId: Long get() = delegate.senderUin override val groupId: Long get() = Group.calculateGroupCodeByGroupUin(delegate.toUin) @@ -39,12 +43,14 @@ internal inline class MessageSourceFromMsg( val delegate: MsgComm.Msg ) : MessageSource { override val time: Long get() = delegate.msgHead.msgTime.toLong() and 0xFFFFFFFF - override val sequenceId: Int get() = delegate.msgHead.msgSeq + override val id: Long + get() = delegate.msgHead.msgSeq.toLong().shl(32) or + delegate.msgBody.richText.attr!!.random.toLong().and(0xFFFFFFFF) + override suspend fun ensureSequenceIdAvailable() { // nothing to do } - override val messageUid: Int get() = delegate.msgBody.richText.attr!!.random // override val sourceMessage: MessageChain get() = delegate.toMessageChain() override val senderId: Long get() = delegate.msgHead.fromUin override val groupId: Long get() = delegate.msgHead.groupInfo!!.groupCode @@ -62,7 +68,7 @@ internal inline class MessageSourceFromMsg( type = 0, time = delegate.msgHead.msgTime, pbReserve = SourceMsg.ResvAttr( - origUids = messageUid.toLong() and 0xffFFffFF + origUids = messageRandom.toLong() and 0xffFFffFF ).toByteArray(SourceMsg.ResvAttr.serializer()), srcMsg = MsgComm.Msg( msgHead = MsgComm.MsgHead( @@ -72,7 +78,7 @@ internal inline class MessageSourceFromMsg( c2cCmd = delegate.msgHead.c2cCmd, msgSeq = delegate.msgHead.msgSeq, msgTime = delegate.msgHead.msgTime, - msgUid = messageUid.toLong() and 0xffFFffFF + msgUid = messageRandom.toLong() and 0xffFFffFF , // ok groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode), isSrcMsg = true diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt index 694dd7277..3054692fd 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt @@ -272,7 +272,7 @@ internal class MessageSvc { } internal class MessageSourceFromSend( - override val messageUid: Int, + val messageRandom: Int, override val time: Long, override val senderId: Long, override val groupId: Long// , @@ -280,19 +280,20 @@ internal class MessageSvc { ) : MessageSource { private lateinit var sequenceIdDeferred: Deferred + @UseExperimental(ExperimentalCoroutinesApi::class) + override val id: Long + get() = sequenceIdDeferred.getCompleted().toLong().shl(32) or + messageRandom.toLong().and(0xFFFFFFFF) + @UseExperimental(MiraiExperimentalAPI::class) fun startWaitingSequenceId(contact: Contact) { sequenceIdDeferred = contact.subscribingGetAsync { - if (it.messageRandom == messageUid) { + if (it.messageRandom == this@MessageSourceFromSend.messageRandom) { it.sequenceId } else null } } - @UseExperimental(ExperimentalCoroutinesApi::class) - override val sequenceId: Int - get() = sequenceIdDeferred.getCompleted() - override suspend fun ensureSequenceIdAvailable() { sequenceIdDeferred.join() } @@ -309,7 +310,7 @@ internal class MessageSvc { crossinline sourceCallback: (MessageSource) -> Unit ): OutgoingPacket { val source = MessageSourceFromSend( - messageUid = Random.nextInt().absoluteValue, + messageRandom = Random.nextInt().absoluteValue, senderId = client.uin, time = currentTimeSeconds + client.timeDifference, groupId = 0// @@ -327,7 +328,7 @@ internal class MessageSvc { client: QQAndroidClient, toUin: Long, message: MessageChain, - source: MessageSource + source: MessageSourceFromSend ): OutgoingPacket = buildOutgoingUniPacket(client) { ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes()) @@ -342,7 +343,7 @@ internal class MessageSvc { ) ), msgSeq = client.atomicNextMessageSequenceId(), - msgRand = source.messageUid, + msgRand = source.messageRandom, syncCookie = SyncCookie(time = source.time).toByteArray(SyncCookie.serializer()) // msgVia = 1 ) @@ -358,7 +359,7 @@ internal class MessageSvc { ): OutgoingPacket { val source = MessageSourceFromSend( - messageUid = Random.nextInt().absoluteValue, + messageRandom = Random.nextInt().absoluteValue, senderId = client.uin, time = currentTimeSeconds + client.timeDifference, groupId = groupCode//, @@ -372,11 +373,11 @@ internal class MessageSvc { * 发送群消息 */ @Suppress("FunctionName") - fun ToGroup( + private fun ToGroup( client: QQAndroidClient, groupCode: Long, message: MessageChain, - source: MessageSource + source: MessageSourceFromSend ): OutgoingPacket = buildOutgoingUniPacket(client) { ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes()) @@ -393,7 +394,7 @@ internal class MessageSvc { ) ), msgSeq = client.atomicNextMessageSequenceId(), - msgRand = source.messageUid, + msgRand = source.messageRandom, syncCookie = EMPTY_BYTE_ARRAY, msgVia = 1 ) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index 24ea3516e..e5ecf61af 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -231,11 +231,13 @@ abstract class Bot : CoroutineScope { * [Bot] 撤回自己的消息不需要权限. * [Bot] 撤回群员的消息需要管理员权限. * + * @param messageId 即 [MessageSource.id] + * * @throws PermissionDeniedException 当 [Bot] 无权限操作时 * @see Bot.recall (扩展函数) 接受参数 [MessageChain] - * @see 更推荐说 + * @see recall 请优先使用这个函数 */ - abstract suspend fun recall(groupId: Long, messageSequenceId: Int, messageUid: Int) + abstract suspend fun recall(groupId: Long, senderId: Long, messageId: Long) @Deprecated("内存使用效率十分低下", ReplaceWith("this.download()"), DeprecationLevel.WARNING) @MiraiExperimentalAPI("未支持") diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt index a00d8021d..42b232bce 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt @@ -12,6 +12,7 @@ package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import net.mamoe.mirai.Bot import net.mamoe.mirai.event.events.BeforeImageUploadEvent import net.mamoe.mirai.event.events.EventCancelledException @@ -20,8 +21,13 @@ import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.recall +import net.mamoe.mirai.recallIn import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.WeakRefProperty +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** @@ -92,8 +98,44 @@ interface Contact : CoroutineScope { override fun toString(): String } +/** + * @see Bot.recall + */ +@MiraiExperimentalAPI +suspend inline fun Contact.recall(source: MessageChain) = this.bot.recall(source) + +/** + * @see Bot.recall + */ +suspend inline fun Contact.recall(source: MessageSource) = this.bot.recall(source) + +/** + * @see Bot.recallIn + */ +@MiraiExperimentalAPI +fun Contact.recallIn( + message: MessageChain, + millis: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext +): Job = this.bot.recallIn(message, millis, coroutineContext) + +/** + * @see Bot.recallIn + */ +fun Contact.recallIn( + source: MessageSource, + millis: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext +): Job = this.bot.recallIn(source, millis, coroutineContext) + +/** + * @see Contact.sendMessage + */ @Suppress("UNCHECKED_CAST") suspend inline fun C.sendMessage(message: Message): MessageReceipt = sendMessage(message.toChain()) as? MessageReceipt ?: error("Internal class cast mistake") +/** + * @see Contact.sendMessage + */ suspend inline fun C.sendMessage(plain: String): MessageReceipt = sendMessage(plain.toMessage()) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt index 5752b8755..f30ee0a84 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt @@ -11,11 +11,16 @@ package net.mamoe.mirai.message import kotlinx.coroutines.Job import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.* +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.Event import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource +import net.mamoe.mirai.recall +import net.mamoe.mirai.recallIn +import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.unsafeWeakRef import kotlin.jvm.JvmName @@ -59,10 +64,13 @@ class GroupMessage( @JvmName("reply2") suspend inline fun MessageChain.quoteReply(): MessageReceipt = quoteReply(this) - suspend inline fun MessageChain.recall() = group.recall(this) - suspend inline fun MessageSource.recall() = group.recall(this) - inline fun MessageSource.recallIn(delay: Long): Job = group.recallIn(this, delay) - inline fun MessageChain.recallIn(delay: Long): Job = group.recallIn(this, delay) + @MiraiExperimentalAPI + suspend inline fun MessageChain.recall() = bot.recall(this) + + suspend inline fun MessageSource.recall() = bot.recall(this) + inline fun MessageSource.recallIn(delay: Long): Job = bot.recallIn(this, delay) + @MiraiExperimentalAPI + inline fun MessageChain.recallIn(delay: Long): Job = bot.recallIn(this, delay) override fun toString(): String = "GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)" 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 d760a88f9..46a666f54 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 @@ -12,8 +12,11 @@ package net.mamoe.mirai.message import kotlinx.atomicfu.atomic import kotlinx.coroutines.Job import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.* +import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.recallIn import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.unsafeWeakRef @@ -27,7 +30,6 @@ import net.mamoe.mirai.utils.unsafeWeakRef * @see QQ.sendMessage 发送群消息, 返回回执(此对象) */ open class MessageReceipt( - val originalMessage: MessageChain, private val source: MessageSource, target: C ) { @@ -54,7 +56,7 @@ open class MessageReceipt( if (_isRecalled.compareAndSet(false, true)) { when (val contact = target) { is Group -> { - contact.recall(source) + contact.bot.recall(source) } is QQ -> { TODO() @@ -77,7 +79,7 @@ open class MessageReceipt( if (_isRecalled.compareAndSet(false, true)) { when (val contact = target) { is Group -> { - return contact.recallIn(source, millis) + return contact.bot.recallIn(source, millis) } is QQ -> { TODO() 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 d7a047a30..e57f8711c 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 @@ -27,9 +27,11 @@ interface MessageSource : Message { companion object Key : Message.Key /** - * 序列号. 若是机器人发出去的消息, 请先 [确保 sequenceId 可用][ensureSequenceIdAvailable] + * 在 Mirai 中使用的 id. + * 高 32 位为 [sequenceId], + * 低 32 位为 [messageRandom] */ - val sequenceId: Int + val id: Long /** * 等待 [sequenceId] 获取, 确保其可用. @@ -38,11 +40,6 @@ interface MessageSource : Message { */ suspend fun ensureSequenceIdAvailable() - /** - * 实际上是个随机数, 但服务器确实是用它当做 uid - */ - val messageUid: Int - /** * 发送时间, 单位为秒 */ @@ -65,11 +62,33 @@ interface MessageSource : Message { } /** - * 消息唯一标识符. 实际上是个随机数, 但服务器确实是用它当做 uid + * 序列号. 若是机器人发出去的消息, 请先 [确保 sequenceId 可用][MessageSource.ensureSequenceIdAvailable] + * @see MessageSource.id */ -val MessageChain.messageUid get() = this[MessageSource].messageUid +val MessageSource.sequenceId: Int get() = (this.id shr 32).toInt() + +/** + * 消息随机数. 由服务器或客户端指定后不能更改. 它是消息 id 的一部分. + * @see MessageSource.id + */ +val MessageSource.messageRandom: Int get() = this.id.toInt() + +// For MessageChain + +/** + * 消息 id. + * @see MessageSource.id + */ +val MessageChain.id: Long get() = this[MessageSource].id /** * 消息序列号, 可能来自服务器也可以发送时赋值, 不唯一. + * @see MessageSource.id */ -val MessageChain.sequenceId get() = this[MessageSource].sequenceId \ No newline at end of file +val MessageChain.sequenceId: Int get() = this[MessageSource].sequenceId + +/** + * 消息随机数. 由服务器或客户端指定后不能更改. 它是消息 id 的一部分. + * @see MessageSource.id + */ +val MessageChain.messageRandom: Int get() = this[MessageSource].messageRandom From bb1879f2cae08d6693e800b6435f5aae3a2ef7da Mon Sep 17 00:00:00 2001 From: ryoii Date: Sat, 22 Feb 2020 22:12:39 +0800 Subject: [PATCH 04/12] Http api follow MessageSource update --- .../kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt | 4 +--- .../kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt index a66b42fd6..c1300b0ef 100644 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt +++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt @@ -99,11 +99,9 @@ fun MessageChain.toDTOChain() = mutableListOf(this[MessageSource].toDTO()).apply fun MessageChainDTO.toMessageChain(contact: Contact) = MessageChain().apply { this@toMessageChain.forEach { add(it.toMessage(contact)) } } -internal fun MessageSource.calMessageId() = (messageRandom.toLong() shl 32) or (sequenceId.toLong() and 0xFFFFFFFF) - @UseExperimental(ExperimentalUnsignedTypes::class) fun Message.toDTO() = when (this) { - is MessageSource -> MessageSourceDTO(calMessageId()) + is MessageSource -> MessageSourceDTO(id) is At -> AtDTO(target, display) is AtAll -> AtAllDTO(0L) is Face -> FaceDTO(id) diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt index b2eb44fd2..e497d56e6 100644 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt +++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt @@ -11,7 +11,6 @@ package net.mamoe.mirai.api.http.queue import net.mamoe.mirai.api.http.data.common.EventDTO import net.mamoe.mirai.api.http.data.common.IgnoreEventDTO -import net.mamoe.mirai.api.http.data.common.calMessageId import net.mamoe.mirai.api.http.data.common.toDTO import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.message.GroupMessage @@ -47,7 +46,7 @@ class MessageQueue : ConcurrentLinkedDeque() { } private fun addQuoteCache(msg: GroupMessage) { - quoteCache[msg.message[MessageSource].calMessageId()] = msg + quoteCache[msg.message[MessageSource].id] = msg if (quoteCache.size > quoteCacheSize) { quoteCache.remove(quoteCache.firstKey()) } From 55427ba505908b027f9d104f1ea86ebb7737d74c Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 22 Feb 2020 22:32:49 +0800 Subject: [PATCH 05/12] Add channel utils --- .../kotlin/net.mamoe.mirai/utils/channels.kt | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt new file mode 100644 index 000000000..5b477338f --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt @@ -0,0 +1,114 @@ +/* + * 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:JvmName("Utils") +@file:JvmMultifileClass + +package net.mamoe.mirai.utils + + +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.readAvailable +import kotlinx.coroutines.io.close +import kotlinx.io.OutputStream +import kotlinx.io.core.Output +import kotlinx.io.pool.useInstance +import net.mamoe.mirai.utils.io.ByteArrayPool +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName + + +/** + * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] + */ +suspend fun ByteReadChannel.copyTo(dst: OutputStream) { + ByteArrayPool.useInstance { + do { + val size = this.readAvailable(it) + dst.write(it, 0, size) + } while (size != 0) + } +} + +/** + * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] + */ +suspend fun ByteReadChannel.copyTo(dst: Output) { + ByteArrayPool.useInstance { + do { + val size = this.readAvailable(it) + dst.writeFully(it, 0, size) + } while (size != 0) + } +} + +/** + * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] + */ +suspend fun ByteReadChannel.copyTo(dst: kotlinx.coroutines.io.ByteWriteChannel) { + ByteArrayPool.useInstance { + do { + val size = this.readAvailable(it) + dst.writeFully(it, 0, size) + } while (size != 0) + } +} + + +// copyAndClose + + +/** + * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] + */ +suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) { + try { + ByteArrayPool.useInstance { + do { + val size = this.readAvailable(it) + dst.write(it, 0, size) + } while (size != 0) + } + } finally { + dst.close() + } +} + +/** + * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] + */ +suspend fun ByteReadChannel.copyAndClose(dst: Output) { + try { + ByteArrayPool.useInstance { + do { + val size = this.readAvailable(it) + dst.writeFully(it, 0, size) + } while (size != 0) + } + } finally { + dst.close() + } +} + +/** + * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] + */ +suspend fun ByteReadChannel.copyAndClose(dst: kotlinx.coroutines.io.ByteWriteChannel) { + try { + ByteArrayPool.useInstance { + do { + val size = this.readAvailable(it) + dst.writeFully(it, 0, size) + } while (size != 0) + } + } finally { + dst.close() + } +} \ No newline at end of file From 692e7c950c5fb418ce37e2fdffae44f1ca5f82f6 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 22 Feb 2020 23:32:47 +0800 Subject: [PATCH 06/12] Image download support, close #49 --- .../net/mamoe/mirai/qqandroid/QQAndroidBot.kt | 28 ++++++++----- .../commonMain/kotlin/net.mamoe.mirai/Bot.kt | 39 +++++++------------ .../net.mamoe.mirai/message/MessagePacket.kt | 22 +++++------ .../kotlin/net.mamoe.mirai/utils/channels.kt | 17 ++++---- .../net/mamoe/mirai/message/MessagePacket.kt | 26 +++++++------ .../net/mamoe/mirai/japt/BlockingBot.java | 15 +++---- .../mirai/japt/internal/BlockingBotImpl.kt | 13 ++----- 7 files changed, 75 insertions(+), 85 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index a7e2f8bd5..a5040dc75 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -9,7 +9,9 @@ package net.mamoe.mirai.qqandroid -import kotlinx.io.core.ByteReadPacket +import io.ktor.client.request.get +import io.ktor.client.statement.HttpResponse +import io.ktor.utils.io.ByteReadChannel import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotImpl import net.mamoe.mirai.contact.* @@ -17,10 +19,9 @@ import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.data.MemberInfo -import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.message.data.MessageSource -import net.mamoe.mirai.message.data.messageRandom -import net.mamoe.mirai.message.data.sequenceId +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.qqandroid.message.CustomFaceFromServer +import net.mamoe.mirai.qqandroid.message.NotOnlineImageFromServer import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl @@ -150,13 +151,20 @@ internal abstract class QQAndroidBotBase constructor( } } - override suspend fun Image.download(): ByteReadPacket { - TODO("not implemented") + override suspend fun Image.url(): String = "http://gchat.qpic.cn" + when (this) { + is NotOnlineImageFromServer -> this.delegate.origUrl + is CustomFaceFromServer -> this.delegate.origUrl + is CustomFaceFromFile -> { + TODO() + } + is NotOnlineImageFromFile -> { + TODO() + } + else -> error("unsupported image class: ${this::class.simpleName}") } - @Suppress("OverridingDeprecatedMember") - override suspend fun Image.downloadAsByteArray(): ByteArray { - TODO("not implemented") + override suspend fun Image.channel(): ByteReadChannel { + return Http.get(url()).content } override suspend fun approveFriendAddRequest(id: Long, remark: String?) { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index e5ecf61af..77e7e1d08 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -11,14 +11,11 @@ package net.mamoe.mirai +import io.ktor.utils.io.ByteReadChannel import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.io.OutputStream -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.IoBuffer -import kotlinx.io.core.use import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.FriendInfo @@ -30,7 +27,6 @@ import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.io.transferTo import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmStatic @@ -226,11 +222,12 @@ abstract class Bot : CoroutineScope { abstract suspend fun recall(source: MessageSource) /** - * 撤回这条消息. + * 撤回一条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息. * * [Bot] 撤回自己的消息不需要权限. * [Bot] 撤回群员的消息需要管理员权限. * + * @param senderId 这条消息的发送人. 可以为 [Bot.uin] 或 [Member.id] * @param messageId 即 [MessageSource.id] * * @throws PermissionDeniedException 当 [Bot] 无权限操作时 @@ -239,15 +236,18 @@ abstract class Bot : CoroutineScope { */ abstract suspend fun recall(groupId: Long, senderId: Long, messageId: Long) - @Deprecated("内存使用效率十分低下", ReplaceWith("this.download()"), DeprecationLevel.WARNING) - @MiraiExperimentalAPI("未支持") - abstract suspend fun Image.downloadAsByteArray(): ByteArray + /** + * 获取图片下载链接 + */ + abstract suspend fun Image.url(): String /** - * 将图片下载到内存中 (使用 [IoBuffer.Pool]) + * 获取图片下载链接并开始下载. + * + * @see ByteReadChannel.copyAndClose + * @see ByteReadChannel.copyTo */ - @MiraiExperimentalAPI("未支持") - abstract suspend fun Image.download(): ByteReadPacket + abstract suspend fun Image.channel(): ByteReadChannel /** * 添加一个好友 @@ -278,20 +278,7 @@ abstract class Bot : CoroutineScope { */ abstract fun close(cause: Throwable? = null) - // region extensions - - final override fun toString(): String { - return "Bot(${uin})" - } - - /** - * 需要调用者自行 close [output] - */ - @MiraiExperimentalAPI("未支持") - suspend inline fun Image.downloadTo(output: OutputStream) = - download().use { input -> input.transferTo(output) } - - // endregion + final override fun toString(): String = "Bot(${uin})" } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt index 36c19d890..81a30f351 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt @@ -11,9 +11,7 @@ package net.mamoe.mirai.message -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.IoBuffer -import kotlinx.io.core.readBytes +import io.ktor.utils.io.ByteReadChannel import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group @@ -128,20 +126,22 @@ abstract class MessagePacketBase : Packet, Bot // endregion // region 下载图片 + + /** - * 将图片下载到内存. + * 获取图片下载链接 * - * 非常不推荐这样做. + * @return "http://gchat.qpic.cn/gchatpic_new/..." */ - @Deprecated("内存使用效率十分低下", ReplaceWith("this.download()"), DeprecationLevel.WARNING) - suspend inline fun Image.downloadAsByteArray(): ByteArray = bot.run { download().readBytes() } - - // TODO: 2020/2/5 为下载图片添加文件系统的存储方式 + suspend inline fun Image.url(): String = bot.run { url() } /** - * 将图片下载到内存缓存中 (使用 [IoBuffer.Pool]) + * 获取图片下载链接并开始下载. + * + * @see ByteReadChannel.copyAndClose + * @see ByteReadChannel.copyTo */ - suspend inline fun Image.download(): ByteReadPacket = bot.run { download() } + suspend inline fun Image.channel(): ByteReadChannel = bot.run { channel() } // endregion } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt index 5b477338f..94cb8cc45 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt @@ -16,7 +16,6 @@ package net.mamoe.mirai.utils import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.readAvailable -import kotlinx.coroutines.io.close import kotlinx.io.OutputStream import kotlinx.io.core.Output import kotlinx.io.pool.useInstance @@ -24,6 +23,7 @@ import net.mamoe.mirai.utils.io.ByteArrayPool import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName +// copyTo /** * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] @@ -49,6 +49,8 @@ suspend fun ByteReadChannel.copyTo(dst: Output) { } } + +/* // 垃圾 kotlin, Unresolved reference: ByteWriteChannel /** * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] */ @@ -60,7 +62,7 @@ suspend fun ByteReadChannel.copyTo(dst: kotlinx.coroutines.io.ByteWriteChannel) } while (size != 0) } } - +*/ // copyAndClose @@ -97,18 +99,19 @@ suspend fun ByteReadChannel.copyAndClose(dst: Output) { } } +/*// 垃圾 kotlin, Unresolved reference: ByteWriteChannel /** * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] */ suspend fun ByteReadChannel.copyAndClose(dst: kotlinx.coroutines.io.ByteWriteChannel) { - try { + dst.close(kotlin.runCatching { ByteArrayPool.useInstance { do { val size = this.readAvailable(it) dst.writeFully(it, 0, size) } while (size != 0) } - } finally { - dst.close() - } -} \ No newline at end of file + }.exceptionOrNull()) +} + + */ \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt index 389598f07..c7d20b92d 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt @@ -11,23 +11,19 @@ package net.mamoe.mirai.message -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import kotlinx.io.core.Input import kotlinx.io.core.use -import kotlinx.io.streams.inputStream import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.toExternalImage +import net.mamoe.mirai.utils.copyAndClose +import net.mamoe.mirai.utils.copyTo import java.awt.image.BufferedImage import java.io.File import java.io.InputStream import java.io.OutputStream import java.net.URL -import javax.imageio.ImageIO /** * 一条从服务器接收到的消息事件. @@ -72,16 +68,22 @@ actual abstract class MessagePacket actual con // endregion 发送图片 (扩展) // region 下载图片 (扩展) - suspend inline fun Image.downloadTo(file: File): Long = file.outputStream().use { downloadTo(it) } + suspend inline fun Image.downloadTo(file: File) = file.outputStream().use { downloadTo(it) } /** - * 这个函数结束后不会关闭 [output]. 请务必解决好 [OutputStream.close] + * 下载图片到 [output] 但不关闭这个 [output] */ - suspend inline fun Image.downloadTo(output: OutputStream): Long = - download().inputStream().use { input -> withContext(Dispatchers.IO) { input.copyTo(output) } } + suspend inline fun Image.downloadTo(output: OutputStream) = channel().copyTo(output) - suspend inline fun Image.downloadAsStream(): InputStream = download().inputStream() - suspend inline fun Image.downloadAsExternalImage(): ExternalImage = withContext(Dispatchers.IO) { download().toExternalImage() } + /** + * 下载图片到 [output] 并关闭这个 [output] + */ + suspend inline fun Image.downloadAndClose(output: OutputStream) = channel().copyAndClose(output) + + /* + suspend inline fun Image.downloadAsStream(): InputStream = channel().asInputStream() + suspend inline fun Image.downloadAsExternalImage(): ExternalImage = withContext(Dispatchers.IO) { downloadAsStream().toExternalImage() } suspend inline fun Image.downloadAsBufferedImage(): BufferedImage = withContext(Dispatchers.IO) { ImageIO.read(downloadAsStream()) } + */ // endregion } \ No newline at end of file diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingBot.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingBot.java index 6b2e17197..51afcd81e 100644 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingBot.java +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingBot.java @@ -1,6 +1,5 @@ package net.mamoe.mirai.japt; -import kotlinx.io.core.ByteReadPacket; import net.mamoe.mirai.Bot; import net.mamoe.mirai.BotAccount; import net.mamoe.mirai.BotFactoryJvmKt; @@ -153,18 +152,16 @@ public interface BlockingBot { // region actions - @NotNull - byte[] downloadAsByteArray(@NotNull Image image); - - @NotNull - ByteReadPacket download(@NotNull Image image); - /** * 下载图片到 {@code outputStream}. - *

* 不会自动关闭 {@code outputStream} */ - void download(@NotNull Image image, @NotNull OutputStream outputStream); + void downloadTo(@NotNull Image image, @NotNull OutputStream outputStream); + + /** + * 下载图片到 {@code outputStream} 并关闭 stream + */ + void downloadAndClose(@NotNull Image image, @NotNull OutputStream outputStream); /** * 添加一个好友 diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt index ac990c210..9bf139816 100644 --- a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt +++ b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt @@ -10,8 +10,6 @@ package net.mamoe.mirai.japt.internal import kotlinx.coroutines.runBlocking -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.readBytes import net.mamoe.mirai.Bot import net.mamoe.mirai.BotAccount import net.mamoe.mirai.contact.QQ @@ -23,10 +21,7 @@ import net.mamoe.mirai.japt.BlockingGroup import net.mamoe.mirai.japt.BlockingQQ import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.toList +import net.mamoe.mirai.utils.* import java.io.OutputStream import java.util.stream.Stream import kotlin.streams.asStream @@ -59,10 +54,8 @@ internal class BlockingBotImpl(private val bot: Bot) : BlockingBot { override fun getGroup(id: Long): BlockingGroup = runBlocking { bot.getGroup(id).blocking() } override fun getNetwork(): BotNetworkHandler = bot.network override fun login() = runBlocking { bot.login() } - override fun downloadAsByteArray(image: Image): ByteArray = bot.run { runBlocking { image.download().readBytes() } } - override fun download(image: Image): ByteReadPacket = bot.run { runBlocking { image.download() } } - override fun download(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.downloadTo(outputStream) } } - + override fun downloadTo(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.channel().copyTo(outputStream) } } + override fun downloadAndClose(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.channel().copyAndClose(outputStream) } } override fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult = runBlocking { bot.addFriend(id, message, remark) } override fun approveFriendAddRequest(id: Long, remark: String?) = runBlocking { bot.approveFriendAddRequest(id, remark) } override fun close(throwable: Throwable?) = bot.close(throwable) From 37416f140189efd20a7f88c0aff89e47860d8ecf Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 22 Feb 2020 23:45:00 +0800 Subject: [PATCH 07/12] Add `nextMessageContaining` and nullable versions --- .../net.mamoe.mirai/message/MessagePacket.kt | 80 +++++++++++++++++-- 1 file changed, 72 insertions(+), 8 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt index 81a30f351..5c2608bcf 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt @@ -20,7 +20,7 @@ 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.event.subscribingGetOrNull import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.* import kotlin.jvm.JvmName @@ -133,7 +133,7 @@ abstract class MessagePacketBase : Packet, Bot * * @return "http://gchat.qpic.cn/gchatpic_new/..." */ - suspend inline fun Image.url(): String = bot.run { url() } + suspend inline fun Image.url(): String = bot.queryImageUrl(this@url) /** * 获取图片下载链接并开始下载. @@ -141,7 +141,7 @@ abstract class MessagePacketBase : Packet, Bot * @see ByteReadChannel.copyAndClose * @see ByteReadChannel.copyTo */ - suspend inline fun Image.channel(): ByteReadChannel = bot.run { channel() } + suspend inline fun Image.channel(): ByteReadChannel = bot.openChannel(this) // endregion } @@ -153,14 +153,12 @@ fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Bo } /** - * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [P] 相同且通过 [筛选][filter] 的 [MessagePacket] + * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同且通过 [筛选][filter] 的 [MessagePacket] * * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值 - * - * @see subscribingGetAsync 本函数的异步版本 */ suspend inline fun > P.nextMessage( timeoutMillis: Long = -1, @@ -172,13 +170,30 @@ suspend inline fun > P.nextMessage( } /** - * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [P] 相同的 [MessagePacket] + * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同且通过 [筛选][filter] 的 [MessagePacket] * * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值 * - * @see subscribingGetAsync 本函数的异步版本 + * @return 消息链. 超时时返回 `null` + */ +suspend inline fun > P.nextMessageOrNull( + timeoutMillis: Long = -1, + crossinline filter: P.(P) -> Boolean +): MessageChain? { + return subscribingGetOrNull(timeoutMillis) { + takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) }?.takeIf { filter(it, it) } + }?.message +} + +/** + * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同的 [MessagePacket] + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 */ suspend inline fun > P.nextMessage( timeoutMillis: Long = -1 @@ -186,4 +201,53 @@ suspend inline fun > P.nextMessage( return subscribingGet(timeoutMillis) { takeIf { this.isContextIdenticalWith(this@nextMessage) } }.message +} + +/** + * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同的 [MessagePacket] + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * + * @return 消息链. 超时时返回 `null` + */ +suspend inline fun > P.nextMessageOrNull( + timeoutMillis: Long = -1 +): MessageChain? { + return subscribingGetOrNull(timeoutMillis) { + takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) } + }?.message +} + +/** + * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同的 [MessagePacket] + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + */ +suspend inline fun MessagePacket<*, *>.nextMessageContaining( + timeoutMillis: Long = -1 +): M { + return subscribingGet, MessagePacket<*, *>>(timeoutMillis) { + takeIf { this.isContextIdenticalWith(this@nextMessageContaining) } + }.message.first() +} + +/** + * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同并含有 [M] 类型的消息的 [MessagePacket] + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * + * @return 指定类型的消息. 超时时返回 `null` + */ +suspend inline fun MessagePacket<*, *>.nextMessageContainingOrNull( + timeoutMillis: Long = -1 +): M? { + return subscribingGetOrNull, MessagePacket<*, *>>(timeoutMillis) { + takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNull) } + }?.message?.first() } \ No newline at end of file From ee2dcc8584ef4f6f3d3db5f4cc959ade2be5e424 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 22 Feb 2020 23:45:40 +0800 Subject: [PATCH 08/12] Fix improper receiver use --- .../kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt | 12 ++++++------ .../src/commonMain/kotlin/net.mamoe.mirai/Bot.kt | 4 ++-- .../net/mamoe/mirai/japt/internal/BlockingBotImpl.kt | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index a5040dc75..5b400fca4 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -151,20 +151,20 @@ internal abstract class QQAndroidBotBase constructor( } } - override suspend fun Image.url(): String = "http://gchat.qpic.cn" + when (this) { - is NotOnlineImageFromServer -> this.delegate.origUrl - is CustomFaceFromServer -> this.delegate.origUrl + override suspend fun queryImageUrl(image: Image): String = "http://gchat.qpic.cn" + when (image) { + is NotOnlineImageFromServer -> image.delegate.origUrl + is CustomFaceFromServer -> image.delegate.origUrl is CustomFaceFromFile -> { TODO() } is NotOnlineImageFromFile -> { TODO() } - else -> error("unsupported image class: ${this::class.simpleName}") + else -> error("unsupported image class: ${image::class.simpleName}") } - override suspend fun Image.channel(): ByteReadChannel { - return Http.get(url()).content + override suspend fun openChannel(image: Image): ByteReadChannel { + return Http.get(queryImageUrl(image)).content } override suspend fun approveFriendAddRequest(id: Long, remark: String?) { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index 77e7e1d08..248ac05f1 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -239,7 +239,7 @@ abstract class Bot : CoroutineScope { /** * 获取图片下载链接 */ - abstract suspend fun Image.url(): String + abstract suspend fun queryImageUrl(image: Image): String /** * 获取图片下载链接并开始下载. @@ -247,7 +247,7 @@ abstract class Bot : CoroutineScope { * @see ByteReadChannel.copyAndClose * @see ByteReadChannel.copyTo */ - abstract suspend fun Image.channel(): ByteReadChannel + abstract suspend fun openChannel(image: Image): ByteReadChannel /** * 添加一个好友 diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt index 9bf139816..3d93a3dbd 100644 --- a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt +++ b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt @@ -54,8 +54,8 @@ internal class BlockingBotImpl(private val bot: Bot) : BlockingBot { override fun getGroup(id: Long): BlockingGroup = runBlocking { bot.getGroup(id).blocking() } override fun getNetwork(): BotNetworkHandler = bot.network override fun login() = runBlocking { bot.login() } - override fun downloadTo(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.channel().copyTo(outputStream) } } - override fun downloadAndClose(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.channel().copyAndClose(outputStream) } } + override fun downloadTo(image: Image, outputStream: OutputStream) = bot.run { runBlocking { openChannel(image).copyTo(outputStream) } } + override fun downloadAndClose(image: Image, outputStream: OutputStream) = bot.run { runBlocking { openChannel(image).copyAndClose(outputStream) } } override fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult = runBlocking { bot.addFriend(id, message, remark) } override fun approveFriendAddRequest(id: Long, remark: String?) = runBlocking { bot.approveFriendAddRequest(id, remark) } override fun close(throwable: Throwable?) = bot.close(throwable) From 9a72a66723adba285e737339813d6acb38cd3f05 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 23 Feb 2020 00:04:40 +0800 Subject: [PATCH 09/12] Add references --- .../net.mamoe.mirai/message/MessagePacket.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt index 5c2608bcf..6e65f8a5f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt @@ -159,6 +159,8 @@ fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Bo * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值 + * + * @see subscribingGet */ suspend inline fun > P.nextMessage( timeoutMillis: Long = -1, @@ -176,8 +178,9 @@ suspend inline fun > P.nextMessage( * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值 - * * @return 消息链. 超时时返回 `null` + * + * @see subscribingGetOrNull */ suspend inline fun > P.nextMessageOrNull( timeoutMillis: Long = -1, @@ -194,6 +197,8 @@ suspend inline fun > P.nextMessageOrNull( * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * + * @see subscribingGet */ suspend inline fun > P.nextMessage( timeoutMillis: Long = -1 @@ -209,8 +214,9 @@ suspend inline fun > P.nextMessage( * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 - * * @return 消息链. 超时时返回 `null` + * + * @see subscribingGetOrNull */ suspend inline fun > P.nextMessageOrNull( timeoutMillis: Long = -1 @@ -226,6 +232,8 @@ suspend inline fun > P.nextMessageOrNull( * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * + * @see subscribingGet */ suspend inline fun MessagePacket<*, *>.nextMessageContaining( timeoutMillis: Long = -1 @@ -241,8 +249,9 @@ suspend inline fun MessagePacket<*, *>.nextMessageContaini * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 - * * @return 指定类型的消息. 超时时返回 `null` + * + * @see subscribingGetOrNull */ suspend inline fun MessagePacket<*, *>.nextMessageContainingOrNull( timeoutMillis: Long = -1 From 3859ce6dafd8e24fc8b23a31b38b266a3203a5c9 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 23 Feb 2020 00:21:49 +0800 Subject: [PATCH 10/12] Concurrent event processing --- .../event/internal/InternalEventListeners.kt | 29 ++++--- .../net.mamoe.mirai/event/subscriber.kt | 7 ++ .../utils/LockFreeLinkedList.kt | 76 +++++++++++-------- 3 files changed, 71 insertions(+), 41 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt index c09b5f303..8ae42e95d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt @@ -10,6 +10,8 @@ package net.mamoe.mirai.event.internal import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.EventDisabled import net.mamoe.mirai.event.Listener @@ -73,6 +75,9 @@ internal class Handler ListeningStatus.LISTENING } } + + @MiraiInternalAPI + override val lock: Mutex = Mutex() } /** @@ -138,23 +143,29 @@ internal object EventListenerManager { // inline: NO extra Continuation @Suppress("UNCHECKED_CAST") -internal suspend inline fun Event.broadcastInternal() { - if (EventDisabled) return +internal suspend inline fun Event.broadcastInternal() = coroutineScope { + if (EventDisabled) return@coroutineScope EventLogger.info { "Event broadcast: $this" } - val listeners = this::class.listeners() - callAndRemoveIfRequired(listeners) + val listeners = this@broadcastInternal::class.listeners() + callAndRemoveIfRequired(this@broadcastInternal, listeners) listeners.supertypes.forEach { - callAndRemoveIfRequired(it.listeners()) + callAndRemoveIfRequired(this@broadcastInternal, it.listeners()) } } -private suspend inline fun E.callAndRemoveIfRequired(listeners: EventListeners) { +@UseExperimental(MiraiInternalAPI::class) +private fun CoroutineScope.callAndRemoveIfRequired(event: E, listeners: EventListeners) { // atomic foreach - listeners.forEach { - if (it.onEvent(this) == ListeningStatus.STOPPED) { - listeners.remove(it) // atomic remove + listeners.forEachNode { node -> + launch { + val listener = node.nodeValue + listener.lock.withLock { + if (!node.isRemoved() && listener.onEvent(event) == ListeningStatus.STOPPED) { + listeners.remove(listener) // atomic remove + } + } } } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt index 9547437fd..9470f7005 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.sync.Mutex import net.mamoe.mirai.Bot import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.internal.Handler @@ -51,6 +52,12 @@ enum class ListeningStatus { * 取消监听: [complete] */ interface Listener : CompletableJob { + /** + * [onEvent] 的锁 + */ + @MiraiInternalAPI + val lock: Mutex + suspend fun onEvent(event: E): ListeningStatus } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt index 1acc1a462..95e45b76d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt @@ -132,7 +132,7 @@ open class LockFreeLinkedList { addLastNode(element.asNode(tail)) } - private fun addLastNode(node: Node) { + private fun addLastNode(node: LockFreeLinkedListNode) { while (true) { val tail = head.iterateBeforeFirst { it === tail } // find the last node. if (tail.nextNodeRef.compareAndSet(this.tail, node)) { // ensure the last node is the last node @@ -146,9 +146,9 @@ open class LockFreeLinkedList { */ @Suppress("DuplicatedCode") open fun addAll(iterable: Iterable) { - var firstNode: Node? = null + var firstNode: LockFreeLinkedListNode? = null - var currentNode: Node? = null + var currentNode: LockFreeLinkedListNode? = null iterable.forEach { val nextNode = it.asNode(tail) if (firstNode == null) { @@ -166,9 +166,9 @@ open class LockFreeLinkedList { */ @Suppress("DuplicatedCode") open fun addAll(iterable: Sequence) { - var firstNode: Node? = null + var firstNode: LockFreeLinkedListNode? = null - var currentNode: Node? = null + var currentNode: LockFreeLinkedListNode? = null iterable.forEach { val nextNode = it.asNode(tail) if (firstNode == null) { @@ -190,7 +190,7 @@ open class LockFreeLinkedList { val node = LazyNode(tail, supplier) while (true) { - var current: Node = head + var current: LockFreeLinkedListNode = head findLastNode@ while (true) { if (current.isValidElementNode() && filter(current.nodeValue)) @@ -208,13 +208,14 @@ open class LockFreeLinkedList { } @PublishedApi // limitation by atomicfu - internal fun Node.compareAndSetNextNodeRef(expect: Node, update: Node) = this.nextNodeRef.compareAndSet(expect, update) + internal fun LockFreeLinkedListNode.compareAndSetNextNodeRef(expect: LockFreeLinkedListNode, update: LockFreeLinkedListNode) = + this.nextNodeRef.compareAndSet(expect, update) override fun toString(): String = joinToString() @Suppress("unused") internal fun getLinkStructure(): String = buildString { - head.childIterateReturnsLastSatisfying>({ + head.childIterateReturnsLastSatisfying>({ append(it.toString()) append(" <- ") it.nextNode @@ -240,7 +241,7 @@ open class LockFreeLinkedList { // physically remove: try to fix the link - var next: Node = toRemove.nextNode + var next: LockFreeLinkedListNode = toRemove.nextNode while (next !== tail && next.isRemoved()) { next = next.nextNode } @@ -269,7 +270,7 @@ open class LockFreeLinkedList { // physically remove: try to fix the link - var next: Node = toRemove.nextNode + var next: LockFreeLinkedListNode = toRemove.nextNode while (next !== tail && next.isRemoved()) { next = next.nextNode } @@ -282,7 +283,7 @@ open class LockFreeLinkedList { /** * 动态计算的大小 */ - val size: Int get() = head.countChildIterate>({ it.nextNode }, { it !is Tail }) - 1 // empty head is always included + val size: Int get() = head.countChildIterate>({ it.nextNode }, { it !is Tail }) - 1 // empty head is always included open operator fun contains(element: E): Boolean { forEach { if (it == element) return true } @@ -295,7 +296,7 @@ open class LockFreeLinkedList { open fun isEmpty(): Boolean = head.allMatching { it.isValidElementNode().not() } inline fun forEach(block: (E) -> Unit) { - var node: Node = head + var node: LockFreeLinkedListNode = head while (true) { if (node === tail) return node.letValueIfValid(block) @@ -303,6 +304,15 @@ open class LockFreeLinkedList { } } + inline fun forEachNode(block: (LockFreeLinkedListNode) -> Unit) { + var node: LockFreeLinkedListNode = head + while (true) { + if (node === tail) return + node.letValueIfValid { block(node) } + node = node.nextNode + } + } + @Suppress("unused") open fun clear() { val first = head.nextNode @@ -638,14 +648,14 @@ open class LockFreeLinkedList { // region internal @Suppress("NOTHING_TO_INLINE") -private inline fun E.asNode(nextNode: Node): Node = Node(nextNode, this) +private inline fun E.asNode(nextNode: LockFreeLinkedListNode): LockFreeLinkedListNode = LockFreeLinkedListNode(nextNode, this) /** * Self-iterate using the [iterator], until [mustBeTrue] returns `false`. * Returns the element at the last time when the [mustBeTrue] returns `true` */ @PublishedApi -internal inline fun > N.childIterateReturnsLastSatisfying(iterator: (N) -> N, mustBeTrue: (N) -> Boolean): N { +internal inline fun > N.childIterateReturnsLastSatisfying(iterator: (N) -> N, mustBeTrue: (N) -> Boolean): N { if (!mustBeTrue(this)) return this var value: N = this @@ -703,9 +713,9 @@ private inline fun E.countChildIterate(iterator: (E) -> E, mustBeTrue: (E) - @PublishedApi internal class LazyNode @PublishedApi internal constructor( - nextNode: Node, + nextNode: LockFreeLinkedListNode, private val valueComputer: () -> E -) : Node(nextNode, null) { +) : LockFreeLinkedListNode(nextNode, null) { private val initialized = atomic(false) private val value: AtomicRef = atomic(null) @@ -727,20 +737,19 @@ internal class LazyNode @PublishedApi internal constructor( } @PublishedApi -internal class Head(nextNode: Node) : Node(nextNode, null) { +internal class Head(nextNode: LockFreeLinkedListNode) : LockFreeLinkedListNode(nextNode, null) { override fun toString(): String = "Head" override val nodeValue: Nothing get() = error("Internal error: trying to get the value of a Head") } @PublishedApi -internal open class Tail : Node(null, null) { +internal open class Tail : LockFreeLinkedListNode(null, null) { override fun toString(): String = "Tail" override val nodeValue: Nothing get() = error("Internal error: trying to get the value of a Tail") } -@PublishedApi -internal open class Node( - nextNode: Node?, +open class LockFreeLinkedListNode( + nextNode: LockFreeLinkedListNode?, private var initialNodeValue: E? ) { /* @@ -754,10 +763,11 @@ internal open class Node( open val nodeValue: E get() = initialNodeValue ?: error("Internal error: nodeValue is not initialized") - val removed = atomic(false) + @PublishedApi + internal val removed = atomic(false) @Suppress("LeakingThis") - val nextNodeRef: AtomicRef> = atomic(nextNode ?: this) + internal val nextNodeRef: AtomicRef> = atomic(nextNode ?: this) inline fun letValueIfValid(block: (E) -> R): R? { if (!this.isValidElementNode()) { @@ -770,7 +780,8 @@ internal open class Node( /** * Short cut for accessing [nextNodeRef] */ - var nextNode: Node + @PublishedApi + internal var nextNode: LockFreeLinkedListNode get() = nextNodeRef.value set(value) { nextNodeRef.value = value @@ -779,7 +790,7 @@ internal open class Node( /** * Returns the former node of the last node whence [filter] returns true */ - inline fun iterateBeforeFirst(filter: (Node) -> Boolean): Node = + inline fun iterateBeforeFirst(filter: (LockFreeLinkedListNode) -> Boolean): LockFreeLinkedListNode = this.childIterateReturnsLastSatisfying({ it.nextNode }, { !filter(it) }) /** @@ -788,7 +799,8 @@ internal open class Node( * Head, which is this, is also being tested. * [Tail], is not being tested. */ - inline fun allMatching(condition: (Node) -> Boolean): Boolean = this.childIterateReturnsLastSatisfying({ it.nextNode }, condition) !is Tail + inline fun allMatching(condition: (LockFreeLinkedListNode) -> Boolean): Boolean = + this.childIterateReturnsLastSatisfying({ it.nextNode }, condition) !is Tail /** * Stop on and returns the former element of the element that is [equals] to the [element] @@ -796,23 +808,23 @@ internal open class Node( * E.g.: for `head <- 1 <- 2 <- 3 <- tail`, `iterateStopOnNodeValue(2)` returns the node whose value is 1 */ @Suppress("NOTHING_TO_INLINE") - internal inline fun iterateBeforeNodeValue(element: E): Node = this.iterateBeforeFirst { it.isValidElementNode() && it.nodeValue == element } + internal inline fun iterateBeforeNodeValue(element: E): LockFreeLinkedListNode = + this.iterateBeforeFirst { it.isValidElementNode() && it.nodeValue == element } } -@PublishedApi // DO NOT INLINE: ATOMIC OPERATION -internal fun Node.isRemoved() = this.removed.value +fun LockFreeLinkedListNode.isRemoved() = this.removed.value @PublishedApi @Suppress("NOTHING_TO_INLINE") -internal inline fun Node<*>.isValidElementNode(): Boolean = !isHead() && !isTail() && !isRemoved() +internal inline fun LockFreeLinkedListNode<*>.isValidElementNode(): Boolean = !isHead() && !isTail() && !isRemoved() @PublishedApi @Suppress("NOTHING_TO_INLINE") -internal inline fun Node<*>.isHead(): Boolean = this is Head +internal inline fun LockFreeLinkedListNode<*>.isHead(): Boolean = this is Head @PublishedApi @Suppress("NOTHING_TO_INLINE") -internal inline fun Node<*>.isTail(): Boolean = this is Tail +internal inline fun LockFreeLinkedListNode<*>.isTail(): Boolean = this is Tail // end region \ No newline at end of file From 94dd54e5ddce8319e605a7933d8d5d7f2870e388 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 23 Feb 2020 00:34:07 +0800 Subject: [PATCH 11/12] 0.20.0 --- CHANGELOG.md | 30 +++++++++++++++++++++++------- gradle.properties | 2 +- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5096266aa..d41856370 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ 开发版本. 频繁更新, 不保证高稳定性 +## `0.20.0` 2020/2/23 + +### mirai-core +- 支持图片下载: `image.channel(): ByteReadChannel`, `image.url()` + +- 添加 `LockFreeLinkedList.iterator` +- 添加 `LockFreeLinkedList.forEachNode` + +- 并行处理事件监听 +- 添加 `nextMessageContaining` 和相关可空版本 + +- '撤回' 从 `Contact` 移动到 `Bot` +- 删除 `MessageSource.sourceMessage` +- 让 MessageSource 拥有唯一的 long 类型 id, 删除原 `uid` 和 `sequence` 结构. +- 修复 `Message.eq` 歧义 + ## `0.19.1` 2020/2/21 ### mirai-core @@ -255,16 +271,16 @@ TIMPC 这些模块都继承自 `mirai-core`. 现在, 要使用 mirai, 必须依赖于特定的协议模块, 如 `mirai-core-timpc`. 查阅 API 时请查看 `mirai-core`. -每个模块只提供少量的额外方法. 我们会给出详细列表. +每个模块只提供少量的额外方法. 我们会给出详细列表. 在目前的开发中您无需考虑多协议兼容. **Bot 构造** 协议抽象后构造 Bot 需指定协议的 `BotFactory`. -在 JVM 平台, Mirai 通过 classname 自动加载协议模块的 `BotFactory`, 因此若您只使用一套协议, 则无需修改现行源码 +在 JVM 平台, Mirai 通过 classname 自动加载协议模块的 `BotFactory`, 因此若您只使用一套协议, 则无需修改现行源码 **事件** -大部分事件包名修改. +大部分事件包名修改. **UInt -> Long** 修改全部 QQ ID, Group ID 的类型由 UInt 为 Long. @@ -314,21 +330,21 @@ TIMPC - 禁言的扩展函数现在会传递实际函数的返回值 ## `0.7.0` *2019/12/04* -协议 +协议 - 重新分析验证码包, 解决一些无法解析的情况. (这可能会产生新的问题, 遇到后请提交 issue) - 重新分析提交密码包 - *提交验证码仍可能出现问题 (已在 `0.7.5` 修复)* -功能 +功能 - XML 消息 DSL 构造支持 (实验性) (暂不支持发送) - 群成员列表现在包含群主 (原本就应该包含) -- 在消息事件处理中添加获取 `.qq()` 和 `.group()` 的扩展函数. +- 在消息事件处理中添加获取 `.qq()` 和 `.group()` 的扩展函数. - 现在处理群消息时 sender 为 Member (以前为 QQ) - 修改 `Message.concat` 为 `Message.followedBy` - 修改成员权限 `OPERATOR` 为 `ADMINISTRATOR` - **bot.subscribeAll<>() 等函数的 handler lambda 的 receiver 由 Bot 改变为 BotSession**; 此变动不会造成现有代码的修改, 但并不兼容旧版本编译的代码 -性能优化 +性能优化 - 内联 ContactList - 2 个 Contact.sendMessage 重载改为内联扩展函数 **(需要添加 import)** - 其他小优化 diff --git a/gradle.properties b/gradle.properties index 751900c55..8cc8dee39 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # style guide kotlin.code.style=official # config -mirai_version=0.19.1 +mirai_version=0.20.0 mirai_japt_version=1.0.1 kotlin.incremental.multiplatform=true kotlin.parallel.tasks.in.project=true From c441043de0f609a74c8270d4f54c2be629f08626 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 23 Feb 2020 00:43:22 +0800 Subject: [PATCH 12/12] Japt 1.1.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 8cc8dee39..261340417 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ kotlin.code.style=official # config mirai_version=0.20.0 -mirai_japt_version=1.0.1 +mirai_japt_version=1.1.0 kotlin.incremental.multiplatform=true kotlin.parallel.tasks.in.project=true # kotlin