mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 02:12:37 +08:00
Merge remote-tracking branch 'mirai/dev' into dev
This commit is contained in:
commit
622cf31175
@ -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"
|
||||
|
||||
const val core = project
|
||||
const val console = project
|
||||
|
@ -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 {
|
||||
<dependency>
|
||||
<groupId>net.mamoe</groupId>
|
||||
<artifactId>mirai-core-jvm</artifactId>
|
||||
<version>2.2.1</version> <!-- 替换版本为你需要的版本 -->
|
||||
<version>2.2.2</version> <!-- 替换版本为你需要的版本 -->
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
@ -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-文档)
|
||||
|
||||
## 事件通道
|
||||
|
||||
|
@ -4,16 +4,16 @@
|
||||
- [消息系统](#消息系统)
|
||||
- [消息类型](#消息类型)
|
||||
- [消息元素](#消息元素)
|
||||
- [转义规则](#转义规则)
|
||||
- [消息链的 mirai 码](#消息链的-mirai-码)
|
||||
- [由 `CodableMessage` 取得 mirai 码字符串](#由-codablemessage-取得-mirai-码字符串)
|
||||
- [由 mirai 码字符串取得 `MessageChain` 实例](#由-mirai-码字符串取得-messagechain-实例)
|
||||
- [消息链](#消息链)
|
||||
- [发送消息](#发送消息)
|
||||
- [构造消息链](#构造消息链)
|
||||
- [元素唯一性](#元素唯一性)
|
||||
- [获取消息链中的消息元素](#获取消息链中的消息元素)
|
||||
- [Mirai 码](#mirai-码)
|
||||
- [转义规则](#转义规则)
|
||||
- [消息链的 mirai 码](#消息链的-mirai-码)
|
||||
- [由 `CodableMessage` 取得 mirai 码字符串](#由-codablemessage-取得-mirai-码字符串)
|
||||
- [由 mirai 码字符串取得 `MessageChain` 实例](#由-mirai-码字符串取得-messagechain-实例)
|
||||
|
||||
## 消息系统
|
||||
|
||||
@ -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<Contact>
|
||||
```
|
||||
|
||||
要发送简单的单元素消息,使用:
|
||||
要发送字符串消息,使用:(第一部分是 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();
|
||||
```
|
||||
|
||||
|
@ -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 文档(见下文)
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 24c0482d50a0d1314a9e07dd70b428c94d3df25a
|
||||
Subproject commit a5481accb5f882d121ff9fc1d55e4e5f3e908e76
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
@ -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<C : Contact> {
|
||||
*/
|
||||
suspend fun sendMessagePacket(
|
||||
originalMessage: Message,
|
||||
transformedMessage: Message,
|
||||
transformedMessage: MessageChain,
|
||||
finalMessage: MessageChain,
|
||||
step: SendMessageStep,
|
||||
): MessageReceipt<C> {
|
||||
@ -125,10 +125,10 @@ internal abstract class SendMessageHandler<C : Contact> {
|
||||
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<C : Contact> {
|
||||
)
|
||||
}
|
||||
|
||||
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 <C : Contact> SendMessageHandler<C>.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 <C : Contact> SendMessageHandler<C>.sendMessage(
|
||||
originalMessage: Message,
|
||||
transformedMessage: Message,
|
||||
step: SendMessageStep,
|
||||
): MessageReceipt<C> = sendMessageImpl(
|
||||
originalMessage,
|
||||
conversionMessageChain(
|
||||
transformSpecialMessages(
|
||||
preConversionTransformedMessage(transformedMessage)
|
||||
)
|
||||
),
|
||||
step
|
||||
)
|
||||
|
||||
/**
|
||||
* Might be recalled with [transformedMessage] `is` [LongMessageInternal] if length estimation failed (sendMessagePacket)
|
||||
*/
|
||||
internal suspend fun <C : Contact> SendMessageHandler<C>.sendMessageImpl(
|
||||
originalMessage: Message,
|
||||
transformedMessage: MessageChain,
|
||||
step: SendMessageStep,
|
||||
): MessageReceipt<C> { // Result cannot be in interface.
|
||||
val chain = transformSpecialMessages(transformedMessage)
|
||||
.convertToLongMessageIfNeeded(step)
|
||||
val chain = transformedMessage.convertToLongMessageIfNeeded(step)
|
||||
|
||||
chain.findIsInstance<QuoteReply>()?.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<FriendImage>().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<ImgStore.GroupPicUp.Response>()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user