From ff8e6e8b80593eab867bb5175891f2bc1bba6832 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 21 Feb 2020 16:29:39 +0800 Subject: [PATCH 01/12] Add sequenceId to MessageSource --- .../mamoe/mirai/qqandroid/message/MessageSourceFromMsg.kt | 2 ++ .../kotlin/net.mamoe.mirai/message/data/MessageSource.kt | 5 +++++ 2 files changed, 7 insertions(+) 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 fe4aab43f..e0d351c50 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 @@ -22,6 +22,7 @@ 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 messageUid: Long get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!! override val sourceMessage: MessageChain get() = delegate.toMessageChain() override val senderId: Long get() = delegate.senderUin @@ -34,6 +35,7 @@ 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 messageUid: Long get() = delegate.msgBody.richText.attr!!.random.toLong() override val sourceMessage: MessageChain get() = delegate.toMessageChain() override val senderId: Long get() = delegate.msgHead.fromUin 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 e5b831b17..6804a18fc 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 @@ -26,6 +26,11 @@ import kotlin.jvm.JvmName interface MessageSource : Message { companion object Key : Message.Key + /** + * 序列号 + */ + val sequenceId: Int + /** * 实际上是个随机数, 但服务器确实是用它当做 uid */ From bd379c3acc6f43d1c1b9678b93212ee13c52cb33 Mon Sep 17 00:00:00 2001 From: jasonczc Date: Fri, 21 Feb 2020 17:36:51 +0800 Subject: [PATCH 02/12] Update docs --- docs/guide_getting_started.md | 4 +- docs/guide_subscribe_events.md | 114 +++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 docs/guide_subscribe_events.md diff --git a/docs/guide_getting_started.md b/docs/guide_getting_started.md index 3cd88b1db..f705ccefd 100644 --- a/docs/guide_getting_started.md +++ b/docs/guide_getting_started.md @@ -69,7 +69,7 @@ JDK要求6以上 ### 4 Try Bot - 在src/main文件夹下新建文件夹,命名为```kotlin``` -- 在```kotlin```下新建包(在```kotlin```文件夹上右键-```New```-```Packages```) 包名为```net.mamoe.mirai.simpleloader``` +- 在```kotlin```下新建包(在```kotlin```文件夹上右键-```New```-```Package```) 包名为```net.mamoe.mirai.simpleloader``` - 在包下新建kotlin文件```MyLoader.kt``` @@ -103,9 +103,9 @@ suspend fun main() { - 本例的功能中,在任意群内任意成员发送包含“舔”字或“刘老板”字样的消息,MiraiBot会回复“刘老板太强了” - 至此,简单的入门已经结束,下面可根据不同的需求参阅wiki进行功能的添加。 +下面,可以尝试对不同事件进行监听[Mirai Guide - Subscribe Events](/docs/guide_subscribe_events.md) ### 此外,还可以使用Maven作为包管理工具 本项目推荐使用gradle,因此不提供详细入门指导 diff --git a/docs/guide_subscribe_events.md b/docs/guide_subscribe_events.md new file mode 100644 index 000000000..de0035001 --- /dev/null +++ b/docs/guide_subscribe_events.md @@ -0,0 +1,114 @@ +# Mirai Guide - Subscribe Event + +由于Mirai项目在快速推进中,因此内容时有变动,本文档的最后更新日期为```2020-02-21```,对应版本```0.17.0``` + +本页面采用Kotlin作为开发语言,**若你希望使用 Java 开发**, 请参阅: [mirai-japt](mirai-japt/README.md) + +本页面是[Mirai Guide - Getting Started](/docs/guide_getting_started.md)的后续Guide + +## 消息事件-Message Event + +首先我们来回顾上一个Guide的源码 + +```kotlin +suspend fun main() { + val qqId = 10000L//Bot的QQ号,需为Long类型,在结尾处添加大写L + val password = "your_password"//Bot的密码 + val miraiBot = Bot(qqId, password).alsoLogin()//新建Bot并登录 + miraiBot.subscribeMessages { + "你好" reply "你好!" + case("at me") { + reply(sender.at() + " 给爷爬 ") + } + + (contains("舔") or contains("刘老板")) { + "刘老板太强了".reply() + } + } + miraiBot.join() // 等待 Bot 离线, 避免主线程退出 +} +``` + +在本例中,```miraiBot```是一个Bot对象,让其登录,然后对```Message Event```进行了监听。 + +对于``````Message Event``````,```Mirai```提供了较其他Event更强大的[MessageSubscribersBuilder](https://github.com/mamoe/mirai/wiki/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt#L140),本例也采用了[MessageSubscribersBuilder](https://github.com/mamoe/mirai/wiki/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt#L140)。其他具体使用方法可以参考[Wiki:Message Event](https://github.com/mamoe/mirai/wiki/Development-Guide---Kotlin#Message-Event)部分。 + + + +## 事件-Event + +上一节中提到的```Message Event```仅仅是众多```Event```的这一种,其他```Event```有群员加入群,离开群,私聊等等... + +具体doc暂不提供,现可翻阅源码[**BotEvents.kt**](https://github.com/mamoe/mirai/blob/master/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt),查看注释。当前事件仍在扩充中,可能有一定不足。 + +下面我们开始示例对一些事件进行监听。 + + + +## 尝试监听事件-Try Subscribe Events + +### 监听加群事件 + +在代码中的```miraiBot.join()```前添加 + +```kotlin +miraiBot.subscribeAlways { + it.group.sendMessage("欢迎 ${it.member.nameCardOrNick} 加入本群!") +} +``` + +本段语句监听了加入群的事件。 + +### 监听禁言事件 + +在代码中添加 + +```kotlin +miraiBot.subscribeAlways (){ + it.group.sendMessage("恭喜老哥 ${it.member.nameCardOrNick} 喜提禁言套餐一份") +} +``` + +在被禁言后,Bot将发送恭喜语句。 + +### 添加后的可执行代码 + +至此,当前的代码为 + +```kotlin +package net.mamoe.mirai.simpleloader + +import kotlinx.coroutines.* +import net.mamoe.mirai.Bot +import net.mamoe.mirai.alsoLogin +import net.mamoe.mirai.event.subscribeMessages +import net.mamoe.mirai.contact.nameCardOrNick +import net.mamoe.mirai.contact.sendMessage +import net.mamoe.mirai.event.events.MemberJoinEvent +import net.mamoe.mirai.event.events.MemberMuteEvent +import net.mamoe.mirai.event.subscribeAlways + +suspend fun main() { + val qqId = 10000L//Bot的QQ号,需为Long类型,在结尾处添加大写L + val password = "your_password"//Bot的密码 + val miraiBot = Bot(qqId, password).alsoLogin()//新建Bot并登录 + miraiBot.subscribeMessages { + "你好" reply "你好!" + case("at me") { + reply(sender.at() + " 给爷爬 ") + } + + (contains("舔") or contains("刘老板")) { + "刘老板太强了".reply() + } + } + miraiBot.subscribeAlways { + it.group.sendMessage("欢迎 ${it.member.nameCardOrNick} 加入本群!") + } + miraiBot.subscribeAlways (){ + it.group.sendMessage("恭喜老哥 ${it.member.nameCardOrNick} 喜提禁言套餐一份") + } + miraiBot.join() // 等待 Bot 离线, 避免主线程退出 +} +``` + From da72d3bbb64702c61e581e7fe6ef4a049efd3231 Mon Sep 17 00:00:00 2001 From: jasonczc Date: Fri, 21 Feb 2020 17:38:24 +0800 Subject: [PATCH 03/12] format docs --- docs/guide_subscribe_events.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide_subscribe_events.md b/docs/guide_subscribe_events.md index de0035001..36c062890 100644 --- a/docs/guide_subscribe_events.md +++ b/docs/guide_subscribe_events.md @@ -1,4 +1,4 @@ -# Mirai Guide - Subscribe Event +# Mirai Guide - Subscribe Events 由于Mirai项目在快速推进中,因此内容时有变动,本文档的最后更新日期为```2020-02-21```,对应版本```0.17.0``` @@ -45,7 +45,7 @@ suspend fun main() { -## 尝试监听事件-Try Subscribe Events +## 尝试监听事件-Try Subscribing Events ### 监听加群事件 From 587fab23e12857f43d292de4962a9d2e398bbf23 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 21 Feb 2020 18:21:58 +0800 Subject: [PATCH 04/12] Support recall, add MessageReceipt --- .../net/mamoe/mirai/qqandroid/ContactImpl.kt | 50 ++++++- .../protocol/data/proto/MsgRevokeUserDef.kt | 38 +++++ .../network/protocol/packet/PacketFactory.kt | 4 +- .../protocol/packet/chat/PbMessageSvc.kt | 91 ++++++++++++ .../packet/chat/receive/MessageSvc.kt | 65 +++++++- .../packet/chat/receive/OnlinePush.kt | 12 +- .../androidPacketTests/clientToServer.kt | 2 + .../kotlin/test/ProtoBufDataClassGenerator.kt | 2 +- .../commonMain/kotlin/net.mamoe.mirai/Bot.kt | 4 +- .../kotlin/net.mamoe.mirai/contact/Contact.kt | 12 +- .../kotlin/net.mamoe.mirai/contact/Group.kt | 90 ++++++++++- .../kotlin/net.mamoe.mirai/contact/QQ.kt | 19 +++ .../kotlin/net.mamoe.mirai/data/GroupInfo.kt | 11 ++ .../event/{Subscribable.kt => Event.kt} | 0 .../kotlin/net.mamoe.mirai/event/linear.kt | 11 ++ ...ageSubscribers.kt => subscribeMessages.kt} | 0 .../event/{Subscribers.kt => subscriber.kt} | 0 .../net.mamoe.mirai/message/GroupMessage.kt | 10 +- .../net.mamoe.mirai/message/MessagePacket.kt | 27 +--- .../net.mamoe.mirai/message/MessageReceipt.kt | 108 ++++++++++++++ .../message/data/MessageChain.kt | 140 +++--------------- .../message/data/MessageSource.kt | 2 +- .../net.mamoe.mirai/message/data/PlainText.kt | 13 +- 23 files changed, 530 insertions(+), 181 deletions(-) create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgRevokeUserDef.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/PbMessageSvc.kt rename mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/{Subscribable.kt => Event.kt} (100%) create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt rename mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/{MessageSubscribers.kt => subscribeMessages.kt} (100%) rename mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/{Subscribers.kt => subscriber.kt} (100%) create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt 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 29a17b2c9..cda9fcc33 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 @@ -16,14 +16,13 @@ import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent -import net.mamoe.mirai.message.data.CustomFaceFromFile -import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.NotOnlineImageFromFile +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.* 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 @@ -66,7 +65,7 @@ internal class QQImpl( override val nick: String get() = friendInfo.nick - override suspend fun sendMessage(message: MessageChain) { + override suspend fun sendMessage(message: MessageChain): MessageReceipt { val event = FriendMessageSendEvent(this, message).broadcast() if (event.isCancelled) { throw EventCancelledException("cancelled by FriendMessageSendEvent") @@ -80,6 +79,7 @@ internal class QQImpl( ).sendAndExpect() is MessageSvc.PbSendMsg.Response.SUCCESS ) { "send message failed" } } + return MessageReceipt(message, this) } override suspend fun uploadImage(image: ExternalImage): Image = try { @@ -325,6 +325,24 @@ internal class GroupImpl( override lateinit var owner: Member + @UseExperimental(MiraiExperimentalAPI::class) + override val botAsMember: Member by lazy { + Member(object : MemberInfo { + override val nameCard: String + get() = bot.nick // TODO: 2020/2/21 机器人在群内的昵称获取 + override val permission: MemberPermission + get() = botPermission + override val specialTitle: String + get() = "" // TODO: 2020/2/21 获取机器人在群里的头衔 + override val muteTimestamp: Int + get() = botMuteRemaining + override val uin: Long + get() = bot.uin + override val nick: String + get() = bot.nick + }) + } + @UseExperimental(MiraiExperimentalAPI::class) override lateinit var botPermission: MemberPermission @@ -340,6 +358,9 @@ internal class GroupImpl( override val members: ContactList = ContactList(members.mapNotNull { if (it.uin == bot.uin) { botPermission = it.permission + if (it.permission == MemberPermission.OWNER) { + owner = botAsMember + } null } else Member(it).also { member -> if (member.permission == MemberPermission.OWNER) { @@ -475,6 +496,18 @@ internal class GroupImpl( TODO("not implemented") } + override suspend fun recall(source: MessageSource) { + if (source.senderId != bot.uin) { + checkBotPermissionOperator() + } + + 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( @@ -498,7 +531,7 @@ internal class GroupImpl( return members.delegate.filteringGetOrNull { it.id == id } } - override suspend fun sendMessage(message: MessageChain) { + override suspend fun sendMessage(message: MessageChain): MessageReceipt { check(!isBotMuted) { "bot is muted. Remaining seconds=$botMuteRemaining" } val event = GroupMessageSendEvent(this, message).broadcast() if (event.isCancelled) { @@ -514,6 +547,11 @@ internal class GroupImpl( response is MessageSvc.PbSendMsg.Response.SUCCESS ) { "send message failed: $response" } } + + ((message.last() as MessageSource) as MessageSvc.PbSendMsg.MessageSourceFromSend) + .startWaitingSequenceId(this) + + return MessageReceipt(message, this) } override suspend fun uploadImage(image: ExternalImage): Image = try { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgRevokeUserDef.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgRevokeUserDef.kt new file mode 100644 index 000000000..86d34da88 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgRevokeUserDef.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.qqandroid.network.protocol.data.proto + +import kotlinx.serialization.SerialId +import kotlinx.serialization.Serializable +import net.mamoe.mirai.qqandroid.io.ProtoBuf + +class MsgRevokeUserDef : ProtoBuf { + @Serializable + class MsgInfoUserDef( + @SerialId(1) val longMessageFlag: Int = 0, + @SerialId(2) val longMsgInfo: List? = null, + @SerialId(3) val fileUuid: List = listOf() + ) : ProtoBuf { + @Serializable + class MsgInfoDef( + @SerialId(1) val msgSeq: Int = 0, + @SerialId(2) val longMsgId: Int = 0, + @SerialId(3) val longMsgNum: Int = 0, + @SerialId(4) val longMsgIndex: Int = 0 + ) : ProtoBuf + } + + @Serializable + class UinTypeUserDef( + @SerialId(1) val fromUinType: Int = 0, + @SerialId(2) val fromGroupCode: Long = 0L, + @SerialId(3) val fileUuid: List = listOf() + ) : ProtoBuf +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt index 55b6dae7b..2c20312dc 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt @@ -14,6 +14,7 @@ import kotlinx.io.pool.useInstance import net.mamoe.mirai.data.Packet import net.mamoe.mirai.event.Event import net.mamoe.mirai.qqandroid.QQAndroidBot +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 @@ -138,7 +139,8 @@ internal object KnownPacketFactories { TroopManagement.GetGroupInfo, TroopManagement.EditGroupNametag, TroopManagement.Kick, - Heartbeat.Alive + Heartbeat.Alive, + PbMessageSvc.PbMsgWithDraw ) object IncomingFactories : List> by mutableListOf( diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/PbMessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/PbMessageSvc.kt new file mode 100644 index 000000000..eab98c991 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/PbMessageSvc.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.qqandroid.network.protocol.packet.chat + +import kotlinx.io.core.ByteReadPacket +import net.mamoe.mirai.data.Packet +import net.mamoe.mirai.qqandroid.QQAndroidBot +import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf +import net.mamoe.mirai.qqandroid.io.serialization.toByteArray +import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf +import net.mamoe.mirai.qqandroid.network.QQAndroidClient +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgRevokeUserDef +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc +import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket +import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory +import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket + +internal class PbMessageSvc { + object PbMsgWithDraw : OutgoingPacketFactory( + "PbMessageSvc.PbMsgWithDraw" + ) { + sealed class Response : Packet { + object Success : Response() { + override fun toString(): String { + return "PbMessageSvc.PbMsgWithDraw.Response.Success" + } + } + + data class Failed( + val result: Int, + val errorMessage: String + ) : Response() + } + + // 12 1A 08 01 10 00 18 E7 C1 AD B8 02 22 0A 08 BF BA 03 10 BF 81 CB B7 03 2A 02 08 00 + fun Group( + client: QQAndroidClient, + groupCode: Long, + messageSequenceId: Int, // 56639 + messageRandom: Int, // 921878719 + messageType: Int = 0 + ): OutgoingPacket = buildOutgoingUniPacket(client) { + writeProtoBuf( + MsgSvc.PbMsgWithDrawReq.serializer(), + MsgSvc.PbMsgWithDrawReq( + groupWithDraw = listOf( + MsgSvc.PbGroupMsgWithDrawReq( + subCmd = 1, + groupType = 0, // 普通群 + groupCode = groupCode, + msgList = listOf( + MsgSvc.PbGroupMsgWithDrawReq.MessageInfo( + msgSeq = messageSequenceId, + msgRandom = messageRandom, + msgType = messageType + ) + ), + userdef = MsgRevokeUserDef.MsgInfoUserDef( + longMessageFlag = 0 + ).toByteArray(MsgRevokeUserDef.MsgInfoUserDef.serializer()) + ) + ) + ) + ) + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + val resp = readProtoBuf(MsgSvc.PbMsgWithDrawResp.serializer()) + resp.groupWithDraw?.firstOrNull()?.let { + if (it.result != 0) { + return Response.Failed(it.result, it.errmsg) + } + return Response.Success + } + resp.c2cWithDraw?.firstOrNull()?.let { + if (it.result != 0) { + return Response.Failed(it.result, it.errmsg) + } + return Response.Success + } + return Response.Failed(-1, "No response") + } + } +} \ No newline at end of file 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 76d8e6e33..1c0ea0752 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 @@ -9,18 +9,25 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact +import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.MultiPacket import net.mamoe.mirai.data.Packet +import net.mamoe.mirai.event.ListeningStatus import net.mamoe.mirai.event.events.BotJoinGroupEvent import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.MemberJoinEvent +import net.mamoe.mirai.event.subscribe import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource +import net.mamoe.mirai.message.data.addOrRemove import net.mamoe.mirai.qqandroid.GroupImpl import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket @@ -262,6 +269,36 @@ internal class MessageSvc { } } + internal class MessageSourceFromSend( + override val messageUid: Long, + override val time: Long, + override val senderId: Long, + override val groupId: Long, + override val sourceMessage: MessageChain + ) : MessageSource { + lateinit var sequenceIdDeferred: CompletableDeferred + + fun startWaitingSequenceId(contact: Contact) { + sequenceIdDeferred = CompletableDeferred() + contact.subscribe { event -> + if (event.messageRandom == messageUid.toInt()) { + sequenceIdDeferred.complete(event.sequenceId) + return@subscribe ListeningStatus.STOPPED + } + + return@subscribe ListeningStatus.LISTENING + } + } + + @UseExperimental(ExperimentalCoroutinesApi::class) + override val sequenceId: Int + get() = sequenceIdDeferred.getCompleted() + + override fun toString(): String { + return "" + } + } + /** * 发送好友消息 */ @@ -271,9 +308,17 @@ internal class MessageSvc { toUin: Long, message: MessageChain ): 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()) + val source = MessageSourceFromSend( + messageUid = Random.nextInt().absoluteValue.toLong() and 0xffffffff, + senderId = client.uin, + time = currentTimeSeconds + client.timeDifference, + groupId = 0, + sourceMessage = message + ) + message.addOrRemove(source) + ///return@buildOutgoingUniPacket writeProtoBuf( MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq( @@ -285,8 +330,8 @@ internal class MessageSvc { ) ), msgSeq = client.atomicNextMessageSequenceId(), - msgRand = Random.nextInt().absoluteValue, - syncCookie = SyncCookie(time = currentTimeSeconds).toByteArray(SyncCookie.serializer()) + msgRand = source.messageUid.toInt(), + syncCookie = SyncCookie(time = source.time).toByteArray(SyncCookie.serializer()) // msgVia = 1 ) ) @@ -302,11 +347,19 @@ internal class MessageSvc { message: MessageChain ): OutgoingPacket = buildOutgoingUniPacket(client) { + val source = MessageSourceFromSend( + messageUid = Random.nextInt().absoluteValue.toLong() and 0xffffffff, + senderId = client.uin, + time = currentTimeSeconds + client.timeDifference, + groupId = groupCode, + sourceMessage = message + ) + message.addOrRemove(source) + ///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()) // DebugLogger.debug("sending group message: " + message.toRichTextElems().contentToString()) - val seq = client.atomicNextMessageSequenceId() ///return@buildOutgoingUniPacket writeProtoBuf( MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq( @@ -317,8 +370,8 @@ internal class MessageSvc { elems = message.toRichTextElems() ) ), - msgSeq = seq, - msgRand = Random.nextInt().absoluteValue, + msgSeq = client.atomicNextMessageSequenceId(), + msgRand = source.messageUid.toInt(), syncCookie = EMPTY_BYTE_ARRAY, msgVia = 1 ) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt index 3811853bf..0f92e0cca 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt @@ -19,6 +19,7 @@ import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.data.MultiPacket import net.mamoe.mirai.data.NoPacket import net.mamoe.mirai.data.Packet +import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.GroupMessage import net.mamoe.mirai.qqandroid.GroupImpl @@ -46,9 +47,14 @@ internal class OnlinePush { /** * 接受群消息 */ - internal object PbPushGroupMsg : IncomingPacketFactory("OnlinePush.PbPushGroupMsg") { + internal object PbPushGroupMsg : IncomingPacketFactory("OnlinePush.PbPushGroupMsg") { + internal class SendGroupMessageReceipt( + val messageRandom: Int, + val sequenceId: Int + ) : Packet, Event + @UseExperimental(ExperimentalStdlibApi::class) - override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): GroupMessage? { + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? { // 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00 if (!bot.firstLoginSucceed) return null val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer()) @@ -56,7 +62,7 @@ internal class OnlinePush { val extraInfo: ImMsgBody.ExtraInfo? = pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo if (pbPushMsg.msg.msgHead.fromUin == bot.uin) { - return null + return SendGroupMessageReceipt(pbPushMsg.msg.msgBody.richText.attr!!.random, pbPushMsg.msg.msgHead.msgSeq) } val group = bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt index 01eae0859..fcc294793 100644 --- a/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt @@ -107,6 +107,8 @@ fun main() { * 顶层方法. TCP 切掉头后直接来这里 */ fun ByteReadPacket.decodeMultiClientToServerPackets() { + DebugLogger.enable() + PacketLogger.enable() println("=======================处理客户端到服务器=======================") var count = 0 while (remaining != 0L) { diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt index d7bc7319d..17eacf023 100644 --- a/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt @@ -25,7 +25,7 @@ fun main() { println( File( """ - E:\Projects\QQAndroidFF\app\src\main\java\tencent\im\statsvc\getonline + E:\Projects\QQAndroidFF\app\src\main\java\tencent\im\msgrevoke """.trimIndent() ) .generateUnarrangedClasses().toMutableList().arrangeClasses().joinToString("\n\n") 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 96fd0db38..11b5b9460 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -84,7 +84,7 @@ abstract class Bot : CoroutineScope { */ @MiraiExperimentalAPI("还未支持") val nick: String - get() = TODO("bot 昵称获取") + get() = ""// TODO("bot 昵称获取") /** * 日志记录器 @@ -175,8 +175,6 @@ abstract class Bot : CoroutineScope { */ abstract suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence - // TODO 目前还不能构造群对象. 这将在以后支持 - // endregion // region network 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 dcb80fe9f..68224bdd0 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 @@ -18,6 +18,7 @@ import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.event.events.ImageUploadEvent 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.utils.ExternalImage import net.mamoe.mirai.utils.WeakRefProperty @@ -40,19 +41,24 @@ interface Contact : CoroutineScope { * * 对于 [QQ], `uin` 与 `id` 是相同的意思. * 对于 [Group], `groupCode` 与 `id` 是相同的意思. + * + * @see QQ.id + * @see Group.id */ val id: Long /** - * 向这个对象发送消息. + * 向这个对象发送消息. 发送成功后 [message] 中会添加 [MessageSource], 此后可以 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息. * * @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable * * @throws EventCancelledException 当发送消息事件被取消 * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息. */ - suspend fun sendMessage(message: MessageChain) + suspend fun sendMessage(message: MessageChain): MessageReceipt /** * 上传一个图片以备发送. @@ -88,4 +94,4 @@ interface Contact : CoroutineScope { suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.toChain()) -suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.singleChain()) \ No newline at end of file +suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.toMessage()) \ No newline at end of file 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 3d618c9aa..689611954 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,11 +11,22 @@ 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.* +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.message.data.quote import net.mamoe.mirai.utils.MiraiExperimentalAPI +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmName /** @@ -83,10 +94,18 @@ interface Group : Contact, CoroutineScope { override val id: Long /** - * 群主 + * 群主. + * + * @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员 */ val owner: Member + /** + * [Bot] 在群内的 [Member] 实例 + */ + @MiraiExperimentalAPI + val botAsMember: Member + /** * 机器人被禁言还剩余多少秒 * @@ -133,6 +152,17 @@ 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]. @@ -142,6 +172,19 @@ interface Group : Contact, CoroutineScope { @JvmName("newMember") fun Member(memberInfo: MemberInfo): Member + /** + * 向这个对象发送消息. 发送成功后 [message] 中会添加 [MessageSource], 此后可以 [引用回复][quote] 或 [撤回][recall] 这条消息. + * + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) + */ + override suspend fun sendMessage(message: MessageChain): MessageReceipt + companion object { /** @@ -184,7 +227,52 @@ 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 delay 延迟的时间, 单位为毫秒 + * @param coroutineContext 额外的 [CoroutineContext] + * @see recall + */ +fun Group.recallIn( + message: MessageSource, + delay: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext +): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) { + kotlinx.coroutines.delay(delay) + recall(message) +} + +/** + * 在一段时间后撤回这条消息. + * + * @param delay 延迟的时间, 单位为毫秒 + * @param coroutineContext 额外的 [CoroutineContext] + * @see recall + */ +fun Group.recallIn( + message: MessageChain, + delay: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext +): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) { + kotlinx.coroutines.delay(delay) + recall(message) +} + /** * 返回机器人是否正在被禁言 + * + * @see Group.botMuteRemaining 剩余禁言时间 */ val Group.isBotMuted: Boolean get() = this.botMuteRemaining != 0 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt index 986c00072..9dcd5aa03 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt @@ -16,6 +16,12 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.data.FriendNameRemark import net.mamoe.mirai.data.PreviousNameList import net.mamoe.mirai.data.Profile +import net.mamoe.mirai.event.events.EventCancelledException +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 /** @@ -68,4 +74,17 @@ interface QQ : Contact, CoroutineScope { */ @MiraiExperimentalAPI("还未支持") suspend fun queryRemark(): FriendNameRemark + + /** + * 向这个对象发送消息. 发送成功后 [message] 中会添加 [MessageSource], 此后可以 [撤回][recall] 这条消息. + * + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) + */ + override suspend fun sendMessage(message: MessageChain): MessageReceipt } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt index 0a594a4b0..9dcb261b5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt @@ -62,4 +62,15 @@ interface GroupInfo { * 机器人被禁言还剩时间, 秒. */ val botMuteRemaining: Int + + /* + /** + * 机器人的头衔 + */ + val botSpecialTitle: String + + /** + * 机器人的昵称 + */ + val botNameCard: String*/ } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Event.kt similarity index 100% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Event.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt new file mode 100644 index 000000000..115c95c3e --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt @@ -0,0 +1,11 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.event + diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt similarity index 100% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt similarity index 100% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt 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 efd272542..6f2b62a97 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 @@ -10,12 +10,11 @@ package net.mamoe.mirai.message import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.Member -import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.contact.* 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.utils.getValue import net.mamoe.mirai.utils.unsafeWeakRef import kotlin.jvm.JvmName @@ -60,6 +59,11 @@ class GroupMessage( @JvmName("reply2") suspend inline fun MessageChain.quoteReply() = quoteReply(this) + suspend inline fun MessageChain.recall() = group.recall(this) + suspend inline fun MessageSource.recall() = group.recall(this) + inline fun MessageSource.recallIn(delay: Long) = group.recallIn(this, delay) + inline fun MessageChain.recallIn(delay: Long) = group.recallIn(this, delay) + override fun toString(): String = "GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)" } \ No newline at end of file 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 a42a778fc..f11d244b6 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 @@ -35,7 +35,7 @@ expect abstract class MessagePacket(bot: Bot) /** * 仅内部使用, 请使用 [MessagePacket] */ // Tips: 在 IntelliJ 中 (左侧边栏) 打开 `Structure`, 可查看类结构 -@Suppress("NOTHING_TO_INLINE") +@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST") @MiraiInternalAPI abstract class MessagePacketBase(_bot: Bot) : Packet, BotEvent { /** @@ -73,20 +73,19 @@ abstract class MessagePacketBase(_bot: Bot) : * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息 * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 */ - suspend inline fun reply(message: MessageChain) = subject.sendMessage(message) + suspend inline fun reply(message: MessageChain): MessageReceipt = subject.sendMessage(message) as MessageReceipt - suspend inline fun reply(message: Message) = subject.sendMessage(message.toChain()) - suspend inline fun reply(plain: String) = subject.sendMessage(plain.singleChain()) + suspend inline fun reply(message: Message): MessageReceipt = subject.sendMessage(message.toChain()) as MessageReceipt + suspend inline fun reply(plain: String): MessageReceipt = subject.sendMessage(plain.toMessage().toChain()) as MessageReceipt @JvmName("reply1") - suspend inline fun String.reply() = reply(this) + suspend inline fun String.reply(): MessageReceipt = reply(this) @JvmName("reply1") - suspend inline fun Message.reply() = reply(this) + suspend inline fun Message.reply(): MessageReceipt = reply(this) @JvmName("reply1") - suspend inline fun MessageChain.reply() = reply(this) - + suspend inline fun MessageChain.reply(): MessageReceipt = reply(this) // endregion // region @@ -135,16 +134,4 @@ abstract class MessagePacketBase(_bot: Bot) : */ suspend inline fun Image.download(): ByteReadPacket = bot.run { download() } // endregion - - @Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getFriend(this.target)")) - fun At.qq(): QQ = bot.getFriend(this.target) - - @Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getFriend(this.toLong())")) - fun Int.qq(): QQ = bot.getFriend(this.coerceAtLeastOrFail(0).toLong()) - - @Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getFriend(this)")) - fun Long.qq(): QQ = bot.getFriend(this.coerceAtLeastOrFail(0)) - - @Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getGroup(this)")) - fun Long.group(): Group = bot.getGroup(this) } \ No newline at end of file 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 new file mode 100644 index 000000000..d84aed165 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.message + +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.Job +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.quote +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.getValue +import net.mamoe.mirai.utils.unsafeWeakRef + +/** + * 发送消息后得到的回执. 可用于撤回. + * + * 此对象持有 [Contact] 的弱引用, [Bot] 离线后将会释放引用, 届时 [target] 将无法访问. + * + * @see Group.sendMessage 发送群消息, 返回回执(此对象) + * @see QQ.sendMessage 发送群消息, 返回回执(此对象) + */ +open class MessageReceipt( + val originalMessage: MessageChain, + target: C +) { + init { + require(target is Group || target is QQ) { "target must be either Group or QQ" } + } + + /** + * 发送目标, 为 [Group] 或 [QQ] + */ + val target: C by target.unsafeWeakRef() + + private val _isRecalled = atomic(false) + + /** + * 判断消息是否已被撤回. + */ + val isRecalled: Boolean get() = _isRecalled.value + + /** + * 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次. + * + * @see Group.recall + * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 + */ + @UseExperimental(MiraiExperimentalAPI::class) + suspend fun recall() { + @Suppress("BooleanLiteralArgument") + if (_isRecalled.compareAndSet(false, true)) { + when (val contact = target) { + is Group -> { + contact.recall(originalMessage) + } + is QQ -> { + TODO() + } + else -> error("Unknown contact type") + } + } else error("message is already or planned to be recalled") + } + + /** + * 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次. + * + * @param delay 延迟时间, 单位为毫秒 + * + * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 + */ + @UseExperimental(MiraiExperimentalAPI::class) + fun recallIn(delay: Long): Job { + @Suppress("BooleanLiteralArgument") + if (_isRecalled.compareAndSet(false, true)) { + when (val contact = target) { + is Group -> { + return contact.recallIn(originalMessage, delay) + } + is QQ -> { + TODO() + } + else -> error("Unknown contact type") + } + } else error("message is already or planned to be recalled") + } + + /** + * 引用这条消息. 仅群消息能被引用 + * + * @see MessageChain.quote 引用一条消息 + * + * @throws IllegalStateException 当此消息不是群消息时 + */ + @UseExperimental(MiraiExperimentalAPI::class) + open fun quote(): MessageChain { + val target = target + check(target is Group) { "quote is only available for GroupMessage" } + return this.originalMessage.quote(target.botAsMember) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt index af0781be1..e917fc625 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt @@ -13,9 +13,6 @@ package net.mamoe.mirai.message.data import net.mamoe.mirai.message.data.NullMessageChain.toString -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.contract import kotlin.js.JsName import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -68,6 +65,15 @@ interface MessageChain : Message, MutableList { } } +/** + * 先删除同类型的消息, 再添加 [message] + */ +fun MessageChain.addOrRemove(message: Message) { + val clazz = message::class + this.removeAll { clazz.isInstance(it) } + this.add(message) +} + /** * 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage] */ @@ -132,6 +138,16 @@ fun MessageChain(vararg messages: Message): MessageChain = if (messages.isEmpty()) EmptyMessageChain() else MessageChainImpl(messages.toMutableList()) +/** + * 构造 [MessageChain] 的快速途径 (无 [Array] 创建) + * 若仅提供一个参数, 请考虑使用 [Message.toChain] 以优化性能 + */ +@JvmName("newChain") +@JsName("newChain") +@Suppress("FunctionName") +fun MessageChain(message: Message): MessageChain = + MessageChainImpl(mutableListOf(message)) + /** * 构造 [MessageChain] */ @@ -141,30 +157,6 @@ fun MessageChain(vararg messages: Message): MessageChain = fun MessageChain(messages: Iterable): MessageChain = MessageChainImpl(messages.toMutableList()) -/** - * 构造单元素的不可修改的 [MessageChain]. 内部类实现为 [SingleMessageChain] - * - * 参数 [delegate] 不能为 [MessageChain] 的实例, 否则将会抛出异常. - * 使用 [Message.toChain] 将帮助提前处理这个问题. - * - * @param delegate 所构造的单元素 [MessageChain] 代表的 [Message] - * @throws IllegalArgumentException 当 [delegate] 为 [MessageChain] 的实例时 - * - * @see Message.toChain receiver 模式 - */ -@JvmName("newSingleMessageChain") -@JsName("newChain") -@MiraiExperimentalAPI -@UseExperimental(ExperimentalContracts::class) -@Suppress("FunctionName") -fun SingleMessageChain(delegate: Message): MessageChain { - contract { - returns() implies (delegate !is MessageChain) - } - require(delegate !is MessageChain) { "delegate for SingleMessageChain should not be any instance of MessageChain" } - return SingleMessageChainImpl(delegate) -} - /** * 得到包含 [this] 的 [MessageChain]. @@ -387,97 +379,3 @@ internal inline class MessageChainImpl constructor( // endregion } -/** - * 单个成员的不可修改的 [MessageChain]. - * - * 在连接时将会把它当做一个普通 [Message] 看待, 但它不能被 [plusAssign] - */ -@PublishedApi -internal inline class SingleMessageChainImpl( - private val delegate: Message -) : Message, MutableList, - MessageChain { - - // region Message override - override operator fun contains(sub: String): Boolean = delegate.contains(sub) - - override fun followedBy(tail: Message): MessageChain { - require(tail !is SingleOnly) { "SingleOnly Message cannot follow another message" } - return if (tail is MessageChain) tail.apply { followedBy(delegate) } - else MessageChain(delegate, tail) - } - - override fun plusAssign(message: Message) = - throw UnsupportedOperationException("SingleMessageChainImpl cannot be plusAssigned") - - override fun toString(): String = delegate.toString() - // endregion - - // region MutableList override - override fun containsAll(elements: Collection): Boolean = elements.all { it == delegate } - - override operator fun get(index: Int): Message = if (index == 0) delegate else throw NoSuchElementException() - override fun indexOf(element: Message): Int = if (delegate == element) 0 else -1 - override fun isEmpty(): Boolean = false - override fun lastIndexOf(element: Message): Int = if (delegate == element) 0 else -1 - override fun add(element: Message): Boolean = throw UnsupportedOperationException() - override fun add(index: Int, element: Message) = throw UnsupportedOperationException() - override fun addAll(index: Int, elements: Collection): Boolean = throw UnsupportedOperationException() - override fun addAll(elements: Collection): Boolean = throw UnsupportedOperationException() - override fun clear() = throw UnsupportedOperationException() - override fun listIterator(): MutableListIterator = object : MutableListIterator { - private var hasNext = true - override fun hasPrevious(): Boolean = !hasNext - override fun nextIndex(): Int = if (hasNext) 0 else -1 - override fun previous(): Message = - if (hasPrevious()) { - hasNext = true - delegate - } else throw NoSuchElementException() - - override fun previousIndex(): Int = if (!hasNext) 0 else -1 - override fun add(element: Message) = throw UnsupportedOperationException() - override fun hasNext(): Boolean = hasNext - override fun next(): Message = - if (hasNext) { - hasNext = false - delegate - } else throw NoSuchElementException() - - override fun remove() = throw UnsupportedOperationException() - override fun set(element: Message) = throw UnsupportedOperationException() - } - - override fun listIterator(index: Int): MutableListIterator = - if (index == 0) listIterator() else throw UnsupportedOperationException() - - override fun remove(element: Message): Boolean = throw UnsupportedOperationException() - override fun removeAll(elements: Collection): Boolean = throw UnsupportedOperationException() - override fun removeAt(index: Int): Message = throw UnsupportedOperationException() - override fun retainAll(elements: Collection): Boolean = throw UnsupportedOperationException() - override fun set(index: Int, element: Message): Message = throw UnsupportedOperationException() - override fun subList(fromIndex: Int, toIndex: Int): MutableList { - return if (fromIndex == 0) when (toIndex) { - 1 -> mutableListOf(this) - 0 -> mutableListOf() - else -> throw UnsupportedOperationException() - } - else throw UnsupportedOperationException() - } - - override fun iterator(): MutableIterator = object : MutableIterator { - private var hasNext = true - override fun hasNext(): Boolean = hasNext - override fun next(): Message = - if (hasNext) { - hasNext = false - delegate - } else throw NoSuchElementException() - - override fun remove() = throw UnsupportedOperationException() - } - - override operator fun contains(element: Message): Boolean = element == delegate - override val size: Int get() = 1 - // endregion -} \ No newline at end of file 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 6804a18fc..e5962e5bf 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 @@ -47,7 +47,7 @@ interface MessageSource : Message { val senderId: Long /** - * 群号码 + * 群号码, 为 0 时则来自好友消息 */ val groupId: Long diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt index c11c1e395..e7dbe30ee 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt @@ -38,15 +38,4 @@ inline class PlainText(val stringValue: String) : Message { * 构造 [PlainText] */ @Suppress("NOTHING_TO_INLINE") -inline fun String.toMessage(): PlainText = PlainText(this) - -/** - * 得到包含作为 [PlainText] 的 [this] 的 [MessageChain]. - * - * @return 唯一成员且不可修改的 [SingleMessageChainImpl] - * - * @see SingleMessageChain - * @see SingleMessageChainImpl - */ -@Suppress("NOTHING_TO_INLINE") -inline fun String.singleChain(): MessageChain = SingleMessageChainImpl(this.toMessage()) \ No newline at end of file +inline fun String.toMessage(): PlainText = PlainText(this) \ No newline at end of file From 97da6fce604a60b16823f69027c9eb9b84e09dc7 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 21 Feb 2020 19:04:29 +0800 Subject: [PATCH 05/12] Ensure `sequenceId` available --- .../kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt | 2 ++ .../mirai/qqandroid/message/MessageSourceFromMsg.kt | 9 +++++++++ .../network/protocol/packet/chat/receive/MessageSvc.kt | 4 ++++ .../kotlin/net.mamoe.mirai/message/MessageReceipt.kt | 5 ----- .../kotlin/net.mamoe.mirai/message/data/MessageSource.kt | 9 ++++++++- 5 files changed, 23 insertions(+), 6 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 cda9fcc33..159082173 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 @@ -501,6 +501,8 @@ internal class GroupImpl( checkBotPermissionOperator() } + source.ensureSequenceIdAvailable() + bot.network.run { val response = PbMessageSvc.PbMsgWithDraw.Group(bot.client, this@GroupImpl.id, source.sequenceId, source.messageUid.toInt()) .sendAndExpect() 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 e0d351c50..f966bc71b 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 @@ -23,6 +23,11 @@ internal inline class MessageSourceFromServer( ) : 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 suspend fun ensureSequenceIdAvailable() { + // nothing to do + } + override val messageUid: Long get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!! override val sourceMessage: MessageChain get() = delegate.toMessageChain() override val senderId: Long get() = delegate.senderUin @@ -36,6 +41,10 @@ internal inline class MessageSourceFromMsg( ) : MessageSource { override val time: Long get() = delegate.msgHead.msgTime.toLong() and 0xFFFFFFFF override val sequenceId: Int get() = delegate.msgHead.msgSeq + override suspend fun ensureSequenceIdAvailable() { + // nothing to do + } + override val messageUid: Long get() = delegate.msgBody.richText.attr!!.random.toLong() override val sourceMessage: MessageChain get() = delegate.toMessageChain() override val senderId: Long get() = delegate.msgHead.fromUin 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 1c0ea0752..b6f8fe122 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 @@ -294,6 +294,10 @@ internal class MessageSvc { override val sequenceId: Int get() = sequenceIdDeferred.getCompleted() + override suspend fun ensureSequenceIdAvailable() { + sequenceIdDeferred.join() + } + override fun toString(): String { return "" } 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 d84aed165..3db21c6b2 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 @@ -42,11 +42,6 @@ open class MessageReceipt( private val _isRecalled = atomic(false) - /** - * 判断消息是否已被撤回. - */ - val isRecalled: Boolean get() = _isRecalled.value - /** * 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次. * 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 e5962e5bf..e0b7239dc 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,10 +27,17 @@ interface MessageSource : Message { companion object Key : Message.Key /** - * 序列号 + * 序列号. 若是机器人发出去的消息, 请先 [确保 sequenceId 可用][ensureSequenceIdAvailable] */ val sequenceId: Int + /** + * 等待 [sequenceId] 获取, 确保其可用. + * + * 若原消息发送失败, 这个方法会等待最多 3 秒随后抛出 [IllegalStateException] + */ + suspend fun ensureSequenceIdAvailable() + /** * 实际上是个随机数, 但服务器确实是用它当做 uid */ From 354cc2172da637c60ad6b9e8be56b66359c2d9d8 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 21 Feb 2020 19:35:59 +0800 Subject: [PATCH 06/12] Japt: adapt for core update --- .../net/mamoe/mirai/japt/BlockingContact.java | 8 ++-- .../net/mamoe/mirai/japt/BlockingGroup.java | 33 +++++++++++++++++ .../java/net/mamoe/mirai/japt/BlockingQQ.java | 37 +++++++++++++++++++ .../japt/internal/BlockingContactsImpl.kt | 13 ++++--- 4 files changed, 81 insertions(+), 10 deletions(-) diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java index 26faed221..6ac2875b5 100644 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java @@ -9,6 +9,7 @@ import net.mamoe.mirai.event.events.EventCancelledException; import net.mamoe.mirai.event.events.ImageUploadEvent; 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.Image; import net.mamoe.mirai.message.data.Message; import net.mamoe.mirai.message.data.MessageChain; @@ -42,8 +43,7 @@ public interface BlockingContact { * @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable */ - // kotlin bug - void sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException; + MessageReceipt sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException; /** * 向这个对象发送消息. @@ -53,7 +53,7 @@ public interface BlockingContact { * @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable */ - void sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException; + MessageReceipt sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException; /** * 向这个对象发送消息. @@ -63,7 +63,7 @@ public interface BlockingContact { * @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable */ - void sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException; + MessageReceipt sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException; /** * 上传一个图片以备发送. diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java index 2ebd58f6b..a70b70921 100644 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java @@ -3,6 +3,9 @@ package net.mamoe.mirai.japt; import net.mamoe.mirai.contact.*; import net.mamoe.mirai.data.MemberInfo; import net.mamoe.mirai.event.events.*; +import net.mamoe.mirai.message.MessageReceipt; +import net.mamoe.mirai.message.data.Message; +import net.mamoe.mirai.message.data.MessageChain; import net.mamoe.mirai.utils.MiraiExperimentalAPI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -152,6 +155,36 @@ public interface BlockingGroup extends BlockingContact { @Nullable BlockingMember getMemberOrNull(long id); + /** + * 向这个对象发送消息. + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable + * @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable + */ + MessageReceipt sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException; + + /** + * 向这个对象发送消息. + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable + * @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable + */ + MessageReceipt sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException; + + /** + * 向这个对象发送消息. + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable + * @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable + */ + MessageReceipt sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException; + /** * 检查此 id 的群成员是否存在 */ diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java index 7fb7afc0e..4108a4384 100644 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java @@ -1,8 +1,14 @@ package net.mamoe.mirai.japt; +import net.mamoe.mirai.contact.QQ; import net.mamoe.mirai.data.FriendNameRemark; import net.mamoe.mirai.data.PreviousNameList; import net.mamoe.mirai.data.Profile; +import net.mamoe.mirai.event.events.EventCancelledException; +import net.mamoe.mirai.event.events.MessageSendEvent; +import net.mamoe.mirai.message.MessageReceipt; +import net.mamoe.mirai.message.data.Message; +import net.mamoe.mirai.message.data.MessageChain; import net.mamoe.mirai.utils.MiraiExperimentalAPI; import org.jetbrains.annotations.NotNull; @@ -47,4 +53,35 @@ public interface BlockingQQ extends BlockingContact { @MiraiExperimentalAPI(message = "还未支持") @NotNull FriendNameRemark queryRemark(); + + /** + * 向这个对象发送消息. + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable + * @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable + */ + MessageReceipt sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException; + + /** + * 向这个对象发送消息. + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable + * @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable + */ + MessageReceipt sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException; + + /** + * 向这个对象发送消息. + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * @see MessageSendEvent.FriendMessageSendEvent 发送好友信息事件, cancellable + * @see MessageSendEvent.GroupMessageSendEvent 发送群消息事件. cancellable + */ + MessageReceipt sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException; + } diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContactsImpl.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContactsImpl.kt index 193b57506..451d39d44 100644 --- a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContactsImpl.kt +++ b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContactsImpl.kt @@ -24,6 +24,7 @@ import net.mamoe.mirai.japt.BlockingBot import net.mamoe.mirai.japt.BlockingGroup import net.mamoe.mirai.japt.BlockingMember import net.mamoe.mirai.japt.BlockingQQ +import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiExperimentalAPI @@ -35,9 +36,9 @@ internal class BlockingQQImpl(private val delegate: QQ) : BlockingQQ { override fun getId(): Long = delegate.id override fun getNick(): String = delegate.nick - override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) } - override fun sendMessage(message: String) = runBlocking { delegate.sendMessage(message.toMessage().toChain()) } - override fun sendMessage(message: Message) = runBlocking { delegate.sendMessage(message.toChain()) } + override fun sendMessage(messages: MessageChain): MessageReceipt = runBlocking { delegate.sendMessage(messages) } + override fun sendMessage(message: String): MessageReceipt = runBlocking { delegate.sendMessage(message.toMessage().toChain()) } + override fun sendMessage(message: Message): MessageReceipt = runBlocking { delegate.sendMessage(message.toChain()) } override fun uploadImage(image: ExternalImage): Image = runBlocking { delegate.uploadImage(image) } @MiraiExperimentalAPI @@ -51,9 +52,9 @@ internal class BlockingQQImpl(private val delegate: QQ) : BlockingQQ { } internal class BlockingGroupImpl(private val delegate: Group) : BlockingGroup { - override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) } - override fun sendMessage(message: String) = runBlocking { delegate.sendMessage(message.toMessage().toChain()) } - override fun sendMessage(message: Message) = runBlocking { delegate.sendMessage(message.toChain()) } + override fun sendMessage(messages: MessageChain): MessageReceipt = runBlocking { delegate.sendMessage(messages) } + override fun sendMessage(message: String): MessageReceipt = runBlocking { delegate.sendMessage(message.toMessage().toChain()) } + override fun sendMessage(message: Message): MessageReceipt = runBlocking { delegate.sendMessage(message.toChain()) } override fun getOwner(): BlockingMember = delegate.owner.blocking() @MiraiExperimentalAPI override fun newMember(memberInfo: MemberInfo): Member = delegate.Member(memberInfo) From 2cd2591ff66f9a5ad825de45b696bc633ab6ada1 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 21 Feb 2020 19:37:19 +0800 Subject: [PATCH 07/12] Add `ContactList.first` and ``ContactList.firstOrNull` --- .../kotlin/net.mamoe.mirai/contact/ContactList.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt index 5f459f5b9..c3e3deb11 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt @@ -40,6 +40,15 @@ class ContactList(@MiraiInternalAPI val delegate: LockFreeLinkedLis fun containsAll(elements: Collection): Boolean = elements.all { contains(it) } fun isEmpty(): Boolean = delegate.isEmpty() inline fun forEach(block: (C) -> Unit) = delegate.forEach(block) + fun first(): C { + forEach { return it } + throw NoSuchElementException() + } + + fun firstOrNull(): C? { + forEach { return it } + return null + } override fun toString(): String = delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")") } From 0015f3817c9accd93d38c3fc8277c3fea449cc8b Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 21 Feb 2020 20:28:48 +0800 Subject: [PATCH 08/12] Unified group imageId --- .../mamoe/mirai/qqandroid/message/messages.kt | 9 ++-- .../net.mamoe.mirai/utils/ExternalImage.kt | 43 ++++++++++++++----- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt index 5d0068691..8f0f42630 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt @@ -15,6 +15,7 @@ import kotlinx.io.core.readUInt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiDebugAPI import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.io.discardExact @@ -249,7 +250,7 @@ internal fun MessageChain.toRichTextElems(): MutableList { internal class CustomFaceFromServer( internal val delegate: ImMsgBody.CustomFace ) : CustomFace() { - override val filepath: String get() = delegate.filePath + override val filepath: String = delegate.filePath override val fileId: Int get() = delegate.fileId override val serverIp: Int get() = delegate.serverIp override val serverPort: Int get() = delegate.serverPort @@ -265,14 +266,14 @@ internal class CustomFaceFromServer( override val size: Int get() = delegate.size override val original: Int get() = delegate.origin override val pbReserve: ByteArray get() = delegate.pbReserve - override val imageId: String get() = delegate.filePath + override val imageId: String = ExternalImage.generateImageId(delegate.md5, imageType) override fun equals(other: Any?): Boolean { return other is CustomFaceFromServer && other.filepath == this.filepath && other.md5.contentEquals(this.md5) } override fun hashCode(): Int { - return filepath.hashCode() + 31 * md5.hashCode() + return imageId.hashCode() + 31 * md5.hashCode() } } @@ -296,7 +297,7 @@ internal class NotOnlineImageFromServer( } override fun hashCode(): Int { - return resourceId.hashCode() + 31 * md5.hashCode() + return imageId.hashCode() + 31 * md5.hashCode() } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt index d389e4119..06447bf96 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt @@ -51,9 +51,39 @@ class ExternalImage( filename: String ): ExternalImage = ExternalImage(width, height, md5, format, data, data.remaining, filename) - fun generateUUID(md5: ByteArray): String{ + fun generateUUID(md5: ByteArray): String { return "${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}" } + + fun generateImageId(md5: ByteArray, imageType: Int): String { + return """{${generateUUID(md5)}}.${determineFormat(imageType)}""" + } + + fun determineImageType(format: String): Int { + return when (format) { + "jpg" -> 1000 + "png" -> 1001 + "webp" -> 1002 + "bmp" -> 1005 + "gig" -> 2000 + "apng" -> 2001 + "sharpp" -> 1004 + else -> 1000 // unsupported, just make it jpg + } + } + + fun determineFormat(imageType: Int): String { + return when (imageType) { + 1000 -> "jpg" + 1001 -> "png" + 1002 -> "webp" + 1005 -> "bmp" + 2000 -> "gig" + 2001 -> "apng" + 1004 -> "sharpp" + else -> "jpg" // unsupported, just make it jpg + } + } } val format: String = @@ -73,16 +103,7 @@ class ExternalImage( * SHARPP: 1004 */ val imageType: Int - get() = when (format) { - "jpg" -> 1000 - "png" -> 1001 - "webp" -> 1002 - "bmp" -> 1005 - "gig" -> 2000 - "apng" -> 2001 - "sharpp" -> 1004 - else -> 1000 // unsupported, just make it jpg - } + get() = determineImageType(format) override fun toString(): String = "[ExternalImage(${width}x$height $format)]" From 14f4f4588d53eb23e6610e7e01c8ae7efba619e6 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 21 Feb 2020 20:29:48 +0800 Subject: [PATCH 09/12] Rename `delay` to `millis` --- .../kotlin/net.mamoe.mirai/contact/Group.kt | 12 +++---- .../net.mamoe.mirai/message/MessageReceipt.kt | 36 +++++++++++++++---- 2 files changed, 35 insertions(+), 13 deletions(-) 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 689611954..02c2d2c07 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 @@ -241,32 +241,32 @@ suspend inline fun Group.recall(message: MessageChain) = this.recall(message[Mes /** * 在一段时间后撤回这条消息. * - * @param delay 延迟的时间, 单位为毫秒 + * @param millis 延迟的时间, 单位为毫秒 * @param coroutineContext 额外的 [CoroutineContext] * @see recall */ fun Group.recallIn( message: MessageSource, - delay: Long, + millis: Long, coroutineContext: CoroutineContext = EmptyCoroutineContext ): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) { - kotlinx.coroutines.delay(delay) + kotlinx.coroutines.delay(millis) recall(message) } /** * 在一段时间后撤回这条消息. * - * @param delay 延迟的时间, 单位为毫秒 + * @param millis 延迟的时间, 单位为毫秒 * @param coroutineContext 额外的 [CoroutineContext] * @see recall */ fun Group.recallIn( message: MessageChain, - delay: Long, + millis: Long, coroutineContext: CoroutineContext = EmptyCoroutineContext ): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) { - kotlinx.coroutines.delay(delay) + kotlinx.coroutines.delay(millis) recall(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 3db21c6b2..6204e77c6 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 @@ -13,8 +13,7 @@ import kotlinx.atomicfu.atomic import kotlinx.coroutines.Job import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.quote +import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.unsafeWeakRef @@ -67,17 +66,17 @@ open class MessageReceipt( /** * 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次. * - * @param delay 延迟时间, 单位为毫秒 + * @param millis 延迟时间, 单位为毫秒 * * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 */ @UseExperimental(MiraiExperimentalAPI::class) - fun recallIn(delay: Long): Job { + fun recallIn(millis: Long): Job { @Suppress("BooleanLiteralArgument") if (_isRecalled.compareAndSet(false, true)) { when (val contact = target) { is Group -> { - return contact.recallIn(originalMessage, delay) + return contact.recallIn(originalMessage, millis) } is QQ -> { TODO() @@ -94,10 +93,33 @@ open class MessageReceipt( * * @throws IllegalStateException 当此消息不是群消息时 */ - @UseExperimental(MiraiExperimentalAPI::class) + @MiraiExperimentalAPI("unstable") open fun quote(): MessageChain { val target = target check(target is Group) { "quote is only available for GroupMessage" } return this.originalMessage.quote(target.botAsMember) } -} \ No newline at end of file + + /** + * 引用这条消息并回复. 仅群消息能被引用 + * + * @see MessageChain.quote 引用一条消息 + * + * @throws IllegalStateException 当此消息不是群消息时 + */ + @MiraiExperimentalAPI("unstable") + suspend fun quoteReply(message: MessageChain) { + target.sendMessage(this.quote() + message) + } +} + +@MiraiExperimentalAPI("unstable") +suspend inline fun MessageReceipt.quoteReply(message: Message) { + return this.quoteReply(message.toChain()) +} + +@MiraiExperimentalAPI("unstable") +suspend inline fun MessageReceipt.quoteReply(message: String) { + return this.quoteReply(message.toMessage().toChain()) +} + From b09dadce999269650482175a824146f09f6a1de8 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 21 Feb 2020 20:29:57 +0800 Subject: [PATCH 10/12] Add `toString` --- .../network/protocol/packet/chat/receive/OnlinePush.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt index 0f92e0cca..06720d94e 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt @@ -51,7 +51,11 @@ internal class OnlinePush { internal class SendGroupMessageReceipt( val messageRandom: Int, val sequenceId: Int - ) : Packet, Event + ) : Packet, Event { + override fun toString(): String { + return "OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt(messageRandom=$messageRandom, sequenceId=$sequenceId)" + } + } @UseExperimental(ExperimentalStdlibApi::class) override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? { From 4f6691acc6fdd3b93a64cdaea127c029769a086c Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 21 Feb 2020 20:30:10 +0800 Subject: [PATCH 11/12] Fix `MessageChain.addOrRemove` --- .../kotlin/net.mamoe.mirai/message/data/MessageChain.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt index e917fc625..9a4c9ad4e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt @@ -68,7 +68,7 @@ interface MessageChain : Message, MutableList { /** * 先删除同类型的消息, 再添加 [message] */ -fun MessageChain.addOrRemove(message: Message) { +fun MessageChain.addOrRemove(message: T) { val clazz = message::class this.removeAll { clazz.isInstance(it) } this.add(message) From e580992e0bc7d0ad2f3c50300891026cf9e86875 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 21 Feb 2020 21:07:00 +0800 Subject: [PATCH 12/12] Fix At --- .../mamoe/mirai/qqandroid/message/messages.kt | 25 +++++++++++++------ .../packet/chat/receive/MessageSvc.kt | 8 ++++-- .../net.mamoe.mirai/message/MessagePacket.kt | 4 +-- .../net.mamoe.mirai/message/data/AtAll.kt | 6 +++-- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt index 8f0f42630..9c1d52245 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt @@ -28,12 +28,13 @@ internal fun At.toJceData(): ImMsgBody.Text { return ImMsgBody.Text( str = text, attr6Buf = buildPacket { - writeShort(1) - writeShort(0) - writeShort(text.length.toShort()) - writeByte(1) - writeInt(target.toInt()) - writeShort(0) + // MessageForText$AtTroopMemberInfo + writeShort(1) // const + writeShort(0) // startPos + writeShort(text.length.toShort()) // textLen + writeByte(0) // flag, may=1 + writeInt(target.toInt()) // uin + writeShort(0) // const }.readBytes() ) } @@ -207,7 +208,15 @@ notOnlineImage=NotOnlineImage#2050019814 { private val atAllData = ImMsgBody.Elem( text = ImMsgBody.Text( str = "@全体成员", - attr6Buf = "00 01 00 00 00 05 01 00 00 00 00 00 00".hexToBytes() + attr6Buf = buildPacket { + // MessageForText$AtTroopMemberInfo + writeShort(1) // const + writeShort(0) // startPos + writeShort("@全体成员".length.toShort()) // textLen + writeByte(1) // flag, may=1 + writeInt(0) // uin + writeShort(0) // const + }.readBytes() ) ) @@ -225,7 +234,7 @@ internal fun MessageChain.toRichTextElems(): MutableList { this.forEach { when (it) { is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue))) - is At -> elements.add(ImMsgBody.Elem(text = it.toJceData())) + is At -> elements.add(ImMsgBody.Elem(text = it.toJceData())).also { elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " "))) } is CustomFaceFromFile -> elements.add(ImMsgBody.Elem(customFace = it.toJceData())) is CustomFaceFromServer -> elements.add(ImMsgBody.Elem(customFace = it.delegate)) is NotOnlineImageFromServer -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate)) 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 b6f8fe122..57bddbfdb 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 @@ -263,6 +263,9 @@ internal class MessageSvc { override fun toString(): String = "MessageSvc.PbSendMsg.Response.SUCCESS" } + /** + * 121: 被限制? 个别号才不能发 + */ data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() { override fun toString(): String = "MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)" @@ -352,13 +355,12 @@ internal class MessageSvc { ): OutgoingPacket = buildOutgoingUniPacket(client) { val source = MessageSourceFromSend( - messageUid = Random.nextInt().absoluteValue.toLong() and 0xffffffff, + messageUid = Random.nextInt().absoluteValue.toLong(), senderId = client.uin, time = currentTimeSeconds + client.timeDifference, groupId = groupCode, sourceMessage = message ) - message.addOrRemove(source) ///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()) @@ -380,6 +382,8 @@ internal class MessageSvc { msgVia = 1 ) ) + + message.addOrRemove(source) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { 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 f11d244b6..5ad3974e6 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 @@ -112,9 +112,9 @@ abstract class MessagePacketBase(_bot: Bot) : /** * 创建 @ 这个账号的消息. 当且仅当消息为群消息时可用. 否则将会抛出 [IllegalArgumentException] */ - inline fun QQ.at(): At = At(this as? Member ?: error("`QQ.at` can only be used in GroupMessage")) + fun QQ.at(): At = At(this as? Member ?: error("`QQ.at` can only be used in GroupMessage")) - inline fun At.member(): Member = (this@MessagePacketBase as? GroupMessage)?.group?.get(this.target) ?: error("`At.member` can only be used in GroupMessage") + fun At.member(): Member = (this@MessagePacketBase as? GroupMessage)?.group?.get(this.target) ?: error("`At.member` can only be used in GroupMessage") // endregion diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt index 60dfdfecf..83e39948f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt @@ -16,7 +16,9 @@ import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** - * "@全体成员" + * "@全体成员". + * + * 非会员每天只能发送 10 次 [AtAll]. 超出部分会被以普通文字看待. * * @see At at 单个群成员 */ @@ -26,7 +28,7 @@ object AtAll : Message, Message.Key { // 自动为消息补充 " " override fun followedBy(tail: Message): MessageChain { - if(tail is PlainText && tail.stringValue.startsWith(' ')){ + if (tail is PlainText && tail.stringValue.startsWith(' ')) { return super.followedBy(tail) } return super.followedBy(PlainText(" ")) + tail