From af034a0a00984deba140e0c4fda051e3a3c4a619 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 31 Jan 2021 18:55:02 +0800 Subject: [PATCH 1/9] Improve SendMessageHandler; Fix image not display in some group; #939 --- .../commonMain/kotlin/contact/GroupImpl.kt | 2 + .../kotlin/contact/SendMessageHandler.kt | 91 ++++++++++++++++--- .../commonMain/kotlin/message/imagesImpl.kt | 8 +- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index 8edb3e22c..980318085 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -156,6 +156,7 @@ internal class GroupImpl( is ImgStore.GroupPicUp.Response.FileExists -> { val resourceId = resource.calculateResourceId() return OfflineGroupImage(imageId = resourceId) + .also { it.fileId = response.fileId.toInt() } .also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() } } is ImgStore.GroupPicUp.Response.RequireUpload -> { @@ -169,6 +170,7 @@ internal class GroupImpl( ) return OfflineGroupImage(imageId = resource.calculateResourceId()) + .also { it.fileId = response.fileId.toInt() } .also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() } } } diff --git a/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt b/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt index cc78d45cf..e10c2c697 100644 --- a/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt +++ b/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt @@ -25,11 +25,11 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.* import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToFriend import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToGroup +import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.castOrNull import net.mamoe.mirai.utils.currentTimeSeconds -import java.lang.UnsupportedOperationException /** * 通用处理器 @@ -105,7 +105,7 @@ internal abstract class SendMessageHandler { */ suspend fun sendMessagePacket( originalMessage: Message, - transformedMessage: Message, + transformedMessage: MessageChain, finalMessage: MessageChain, step: SendMessageStep, ): MessageReceipt { @@ -125,10 +125,10 @@ internal abstract class SendMessageHandler { if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) { return when (step) { SendMessageStep.FIRST -> { - sendMessage(originalMessage, transformedMessage, SendMessageStep.LONG_MESSAGE) + sendMessageImpl(originalMessage, transformedMessage, SendMessageStep.LONG_MESSAGE) } SendMessageStep.LONG_MESSAGE -> { - sendMessage(originalMessage, transformedMessage, SendMessageStep.FRAGMENTED) + sendMessageImpl(originalMessage, transformedMessage, SendMessageStep.FRAGMENTED) } else -> { @@ -219,6 +219,8 @@ internal abstract class SendMessageHandler { ) } + open suspend fun preConversionTransformedMessage(message: Message): Message = message + open suspend fun conversionMessageChain(chain: MessageChain): MessageChain = chain open suspend fun postTransformActions(chain: MessageChain) { @@ -255,15 +257,33 @@ internal suspend fun SendMessageHandler.transformSpecialMessage } /** - * Might be recalled with [transformedMessage] `is` [LongMessageInternal] if length estimation failed (sendMessagePacket) + * Send a message, and covert messages + * + * Don't recall this function. */ internal suspend fun SendMessageHandler.sendMessage( originalMessage: Message, transformedMessage: Message, step: SendMessageStep, +): MessageReceipt = sendMessageImpl( + originalMessage, + conversionMessageChain( + transformSpecialMessages( + preConversionTransformedMessage(transformedMessage) + ) + ), + step +) + +/** + * Might be recalled with [transformedMessage] `is` [LongMessageInternal] if length estimation failed (sendMessagePacket) + */ +internal suspend fun SendMessageHandler.sendMessageImpl( + originalMessage: Message, + transformedMessage: MessageChain, + step: SendMessageStep, ): MessageReceipt { // Result cannot be in interface. - val chain = transformSpecialMessages(transformedMessage) - .convertToLongMessageIfNeeded(step) + val chain = transformedMessage.convertToLongMessageIfNeeded(step) chain.findIsInstance()?.source?.ensureSequenceIdAvailable() @@ -314,11 +334,19 @@ internal class GroupSendMessageHandler( override val senderName: String get() = contact.botAsMember.nameCardOrNick - override suspend fun postTransformActions(chain: MessageChain) { - chain.asSequence().filterIsInstance().forEach { image -> - contact.updateFriendImageForGroupMessage(image) + override suspend fun conversionMessageChain(chain: MessageChain): MessageChain = chain.map { element -> + when (element) { + is OfflineGroupImage -> { + contact.fixImageFileId(element) + element + } + is FriendImage -> { + contact.updateFriendImageForGroupMessage(element) + } + else -> element } - } + }.toMessageChain() + override suspend fun constructSourceFromMusicShareResponse( finalMessage: MessageChain, @@ -341,18 +369,55 @@ internal class GroupSendMessageHandler( } companion object { + private suspend fun GroupImpl.fixImageFileId(image: OfflineGroupImage) { + if (image.fileId == null) { + val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp( + bot.client, + uin = bot.id, + groupCode = this.id, + md5 = image.md5, + size = 1, + ).sendAndExpect(bot) + + when (response) { + is ImgStore.GroupPicUp.Response.Failed -> { + image.fileId = 0 // Failed + } + is ImgStore.GroupPicUp.Response.FileExists -> { + image.fileId = response.fileId.toInt() + } + is ImgStore.GroupPicUp.Response.RequireUpload -> { + image.fileId = response.fileId.toInt() + } + } + } + } + /** * Ensures server holds the cache */ - private suspend fun GroupImpl.updateFriendImageForGroupMessage(image: FriendImage) { + private suspend fun GroupImpl.updateFriendImageForGroupMessage(image: FriendImage): OfflineGroupImage { bot.network.run { - ImgStore.GroupPicUp( + val response = ImgStore.GroupPicUp( bot.client, uin = bot.id, groupCode = id, md5 = image.md5, size = if (image is OnlineFriendImageImpl) image.delegate.fileLen else 0 ).sendAndExpect() + return OfflineGroupImage(image.imageId).also { img -> + when (response) { + is ImgStore.GroupPicUp.Response.FileExists -> { + img.fileId = response.fileId.toInt() + } + is ImgStore.GroupPicUp.Response.RequireUpload -> { + img.fileId = response.fileId.toInt() + } + is ImgStore.GroupPicUp.Response.Failed -> { + img.fileId = 0 + } + } + } } } } diff --git a/mirai-core/src/commonMain/kotlin/message/imagesImpl.kt b/mirai-core/src/commonMain/kotlin/message/imagesImpl.kt index 5e942efa2..d2ba26cf5 100644 --- a/mirai-core/src/commonMain/kotlin/message/imagesImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/imagesImpl.kt @@ -178,6 +178,7 @@ internal fun ImMsgBody.CustomFace.toNotOnlineImage(): ImMsgBody.NotOnlineImage { @Suppress("DEPRECATION") internal fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace { return ImMsgBody.CustomFace( + fileId = this.fileId ?: 0, filePath = this.imageId, picMd5 = this.md5, flag = ByteArray(4), @@ -185,7 +186,9 @@ internal fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace { //_400Url = "/gchatpic_new/000000000/1041235568-2195821338-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2", //_400Width = 351, oldData = oldData, - // pbReserve = CustomFaceExtPb.ResvAttr().toByteArray(CustomFaceExtPb.ResvAttr.serializer()) +// pbReserve = "08 00 10 00 32 00 50 00 78 08".autoHexToBytes(), +// useful = 1, + // pbReserve = CustomFaceExtPb.ResvAttr().toByteArray(CustomFaceExtPb.ResvAttr.serializer()) ) } @@ -258,6 +261,9 @@ internal interface OfflineImage : Image internal data class OfflineGroupImage( override val imageId: String ) : GroupImage(), OfflineImage, DeferredOriginUrlAware { + @Transient + internal var fileId: Int? = null + object Serializer : Image.FallbackSerializer("OfflineGroupImage") override fun getUrl(bot: Bot): String { From 55eed50431312a3cac051ad7e1f9335216546d06 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 31 Jan 2021 22:08:54 +0800 Subject: [PATCH 2/9] 2.2.2-dev-2 --- buildSrc/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index ef1a489ab..36cc399e1 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -12,7 +12,7 @@ import org.gradle.api.attributes.Attribute object Versions { - const val project = "2.2.2-dev-1" + const val project = "2.2.2-dev-2" const val core = project const val console = project From 374de0609c0f12635a82f8f4a1c8e5019cea1e9f Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 31 Jan 2021 23:08:55 +0800 Subject: [PATCH 3/9] 2.2.2 --- buildSrc/src/main/kotlin/Versions.kt | 2 +- mirai-console | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 36cc399e1..600e30066 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -12,7 +12,7 @@ import org.gradle.api.attributes.Attribute object Versions { - const val project = "2.2.2-dev-2" + const val project = "2.2.2" const val core = project const val console = project diff --git a/mirai-console b/mirai-console index 24c0482d5..a5481accb 160000 --- a/mirai-console +++ b/mirai-console @@ -1 +1 @@ -Subproject commit 24c0482d50a0d1314a9e07dd70b428c94d3df25a +Subproject commit a5481accb5f882d121ff9fc1d55e4e5f3e908e76 From daff729de5dd2f4fab895b87a181e74a0fb0e23b Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 1 Feb 2021 00:11:25 +0800 Subject: [PATCH 4/9] Update docs --- docs/ConfiguringProjects.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/ConfiguringProjects.md b/docs/ConfiguringProjects.md index 7dbb1cb8d..b233e8f24 100644 --- a/docs/ConfiguringProjects.md +++ b/docs/ConfiguringProjects.md @@ -11,7 +11,7 @@ | 版本类型 | 版本号 | |:------:|:------------------------------:| -| 稳定 | 2.2.1 | +| 稳定 | 2.2.2 | | 预览 | - | | 开发 | [![Version]][Bintray Download] | @@ -40,7 +40,7 @@ repositories { } dependencies { - api("net.mamoe", "mirai-core", "2.2.1") // 替换为你需要的版本号 + api("net.mamoe", "mirai-core", "2.2.2") // 替换为你需要的版本号 } ``` @@ -64,7 +64,7 @@ repositories { } dependencies { - api('net.mamoe', 'mirai-core', '2.2.1') // 替换为你需要的版本号 + api('net.mamoe', 'mirai-core', '2.2.2') // 替换为你需要的版本号 } ``` @@ -77,7 +77,7 @@ dependencies { mirai 在开发时需要 `net.mamoe:mirai-core-api`, 在运行时需要 `net.mamoe:mirai-core`。可以在开发和编译时只依赖 `mirai-core-api`,会减轻对 IDE 的负担。 ```kotlin dependencies { - val miraiVersion = "2.2.1" // 替换为你需要的版本号 + val miraiVersion = "2.2.2" // 替换为你需要的版本号 api("net.mamoe", "mirai-core-api", miraiVersion) // 编译代码使用 runtimeOnly("net.mamoe", "mirai-core", miraiVersion) // 运行时使用 } @@ -105,7 +105,7 @@ dependencies { net.mamoe mirai-core-jvm - 2.2.1 + 2.2.2 ``` From ceb01cf731eb6b9b94ddd2705decebc6908febe3 Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 1 Feb 2021 00:36:45 +0800 Subject: [PATCH 5/9] Update README.md --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index c9df2f70e..172a98220 100644 --- a/docs/README.md +++ b/docs/README.md @@ -66,7 +66,7 @@ **为了避免遇到各种问题,请仔细阅读。** 1. [JVM 环境和开发准备工作(2 分钟)](Preparations.md#mirai---preparations) -2. **配置项目依赖** +2. **配置项目依赖** (二选一) - 要把 mirai-core 嵌入一个应用使用,请阅读 [配置项目依赖](ConfiguringProjects.md)。 - 要为 [`mirai-console`] 框架开发插件,请阅读 [mirai-console 的配置插件项目](https://github.com/mamoe/mirai-console/blob/master/docs/ConfiguringProjects.md)。 3. 阅读 API 文档(见下文) From c2bac74461c97e2f3773d65c312e7e48166adb0b Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 1 Feb 2021 00:44:22 +0800 Subject: [PATCH 6/9] Update Events.md --- docs/Events.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/Events.md b/docs/Events.md index 47ade2f44..002a48d66 100644 --- a/docs/Events.md +++ b/docs/Events.md @@ -64,7 +64,7 @@ Listener listener = GlobalEventChannel.INSTANCE.subscribeAlways(GroupMessageEven listener.complete(); // 停止监听 ``` -异常默认会被相关 Bot 日志记录。可以在 `subscribeAlways` 添加如下内容来处理异常。 +异常默认会被相关 Bot 日志记录。可以在 `subscribeAlways` 之前添加如下内容来处理异常。 ``` // Kotlin .exceptionHandler { e -> e.printStackTrace() } @@ -73,12 +73,14 @@ listener.complete(); // 停止监听 .exceptionHandler(e -> e.printStackTrace()) ``` -**`GlobalEventChannel` 会监听到来自所有 `Bot` 的事件,如果只希望监听某一个,请使用 `bot.eventChannel`。** +**`GlobalEventChannel` 会监听到来自所有 `Bot` 的事件,如果只希望监听某一个 bot,请使用 `bot.eventChannel`。** -> 现在你可以继续阅读,或跳到下一章 [Messages](Messages.md) +> 现在你可以继续阅读详细了解事件,或: > -> 回到 [目录](#目录) -> [回到 Mirai 文档索引](README.md#mirai-core-api-文档) +> - 跳到下一章 [Messages](Messages.md) +> - [查看事件列表](../mirai-core-api/src/commonMain/kotlin/event/events/README.md#事件) +> - 回到 [目录](#目录) +> - [回到 Mirai 文档索引](README.md#mirai-core-api-文档) ## 事件通道 From 2004aa93cd446fa0e8d08e88ab920b382e8a8369 Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 1 Feb 2021 00:45:56 +0800 Subject: [PATCH 7/9] Update Messages.md --- docs/Messages.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Messages.md b/docs/Messages.md index 43cfc4d4a..7d5063c8c 100644 --- a/docs/Messages.md +++ b/docs/Messages.md @@ -5,15 +5,15 @@ - [消息类型](#消息类型) - [消息元素](#消息元素) - [转义规则](#转义规则) - - [消息链的 mirai 码](#消息链的-mirai-码) - - [由 `CodableMessage` 取得 mirai 码字符串](#由-codablemessage-取得-mirai-码字符串) - - [由 mirai 码字符串取得 `MessageChain` 实例](#由-mirai-码字符串取得-messagechain-实例) - [消息链](#消息链) - [发送消息](#发送消息) - [构造消息链](#构造消息链) - [元素唯一性](#元素唯一性) - [获取消息链中的消息元素](#获取消息链中的消息元素) - [Mirai 码](#mirai-码) + - [消息链的 mirai 码](#消息链的-mirai-码) + - [由 `CodableMessage` 取得 mirai 码字符串](#由-codablemessage-取得-mirai-码字符串) + - [由 mirai 码字符串取得 `MessageChain` 实例](#由-mirai-码字符串取得-messagechain-实例) ## 消息系统 From 21f654039ecc19ca1b7278a9edfe3cec225952f9 Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 1 Feb 2021 00:46:24 +0800 Subject: [PATCH 8/9] Update Messages.md --- docs/Messages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Messages.md b/docs/Messages.md index 7d5063c8c..7a7c04e6b 100644 --- a/docs/Messages.md +++ b/docs/Messages.md @@ -4,13 +4,13 @@ - [消息系统](#消息系统) - [消息类型](#消息类型) - [消息元素](#消息元素) - - [转义规则](#转义规则) - [消息链](#消息链) - [发送消息](#发送消息) - [构造消息链](#构造消息链) - [元素唯一性](#元素唯一性) - [获取消息链中的消息元素](#获取消息链中的消息元素) - [Mirai 码](#mirai-码) + - [转义规则](#转义规则) - [消息链的 mirai 码](#消息链的-mirai-码) - [由 `CodableMessage` 取得 mirai 码字符串](#由-codablemessage-取得-mirai-码字符串) - [由 mirai 码字符串取得 `MessageChain` 实例](#由-mirai-码字符串取得-messagechain-实例) From 7d0d92f1fd7f6ecda1929f421bf0bf92b0643bcf Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 1 Feb 2021 01:09:07 +0800 Subject: [PATCH 9/9] Update Messages.md --- docs/Messages.md | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/docs/Messages.md b/docs/Messages.md index 7a7c04e6b..63c5f1ad2 100644 --- a/docs/Messages.md +++ b/docs/Messages.md @@ -28,16 +28,31 @@ ## 消息类型 -*单个消息元素(`SingleMessage`)*分为 *消息内容(`MessageContent`)* 和 *消息元数据(`MessageMetadata`)*。 +Mirai 支持富文本消息。 + +*单个消息元素(`SingleMessage`)* 分为 *内容(`MessageContent`)* 和 *元数据(`MessageMetadata`)*。 实践中,消息内容和消息元数据会混合存在于消息链中。 +### 内容 + +*内容(`MessageContent`)* 即为 *纯文本*、*提及某人*、*图片*、*语音* 和 *音乐分享* 等**有内容**的数据,一条消息中必须包含内容才能发送。 + +### 元数据 + +*元数据(`MessageMetadata`)* 包含 *来源*、*引用回复* 和 *秀图标识* 等。 + +- *消息来源*(`MessageSource`)存在于每条消息中,包含唯一识别信息,用于撤回和引用回复的定位。 +- *引用回复*(`QuoteReply`)若存在,则会在客户端中解析为本条消息引用了另一条消息。 +- *秀图标识*(`ShowImageFlag`)若存在,则表明这条消息中的图片是以秀图发送(QQ 的一个功能)。 + +元数据与内容的区分就在于,一条消息没有元数据也能显示,但一条消息不能没有内容。**元数据是消息的属性**。 + + > 回到 [目录](#目录) ## 消息元素 -Mirai 支持富文本消息。 - 消息拥有三种转换到字符串的表示方式。 | 方法 | 解释 | @@ -104,7 +119,15 @@ Mirai 支持富文本消息。 suspend fun sendMessage(message: Message): MessageReceipt ``` -要发送简单的单元素消息,使用: +要发送字符串消息,使用:(第一部分是 Kotlin,随后是 Java,下同) +```kotlin +contact.sendMessage("Hello!") +``` +```java +contact.sendMessage("Hello!"); +``` + +发送字符串实际上是在发送纯文本消息。上面的代码相当于: ```kotlin contact.sendMessage(PlainText("Hello!")) ``` @@ -114,10 +137,10 @@ contact.sendMessage(new PlainText("Hello!")); 要发送多元素消息,可将消息使用 `plus` 操作连接: ```kotlin -contact.sendMessage(PlainText("你要的图片是") + Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f")) // 一个纯文本加一个图片 +contact.sendMessage(PlainText("你要的图片是") + Image("{f8f1ab55-bf8e-4236-b55e-955848d7069f}.png")) // 一个纯文本加一个图片 ``` ```java -contact.sendMessage(new PlainText("你要的图片是:").plus(Image.fromId("/f8f1ab55-bf8e-4236-b55e-955848d7069f"))); // 一个纯文本加一个图片 +contact.sendMessage(new PlainText("你要的图片是:").plus(Image.fromId("{f8f1ab55-bf8e-4236-b55e-955848d7069f}.png"))); // 一个纯文本加一个图片 ``` ### 构造消息链 @@ -165,13 +188,18 @@ val chain = buildMessageChain { 方法都位于 `net.mamoe.mirai.message.data.MessageUtils`。 +使用 `newChain`: +```java +MessageChain chain = MessageUtils.newChain(new PlainText("Hello"), Image.fromId("{f8f1ab55-bf8e-4236-b55e-955848d7069f}.png")); +``` + 使用 `MessageChainBuilder`: ```java MessageChain chain = new MessageChainBuilder() .append(new PlainText("string")) .append("string") // 会被构造成 PlainText 再添加, 相当于上一行 .append(AtAll.INSTANCE) - .append(Image.fromId("/f8f1ab55-bf8e-4236-b55e-955848d7069f")) + .append(Image.fromId("{f8f1ab55-bf8e-4236-b55e-955848d7069f}.png")) .build(); ```