Support Dice (#1018)

* Add Dice public API #1012, close #1017

* Extract MarketFaceImpl to separate file

* Dice protocol impl #1012

* Dice refinement

* Add serialization support for Dice

* Add mirai code support for Dice

* Update docs/Messages.md

Co-authored-by: Karlatemp <karlatemp@vip.qq.com>

* Update mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt

Co-authored-by: Karlatemp <karlatemp@vip.qq.com>

* Add dice mirai code test

Co-authored-by: sandtechnology <a1294790523@hotmail.com>
Co-authored-by: lc6a <1952511149@qq.com>
Co-authored-by: Karlatemp <karlatemp@vip.qq.com>
This commit is contained in:
Him188 2021-02-13 11:34:23 +08:00 committed by GitHub
parent d09d810b6f
commit 4ac7d3fa9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 227 additions and 26 deletions

View File

@ -3475,6 +3475,41 @@ public final class net/mamoe/mirai/message/data/CustomMessageMetadata$Companion
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Dice : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MarketFace {
public static final field Key Lnet/mamoe/mirai/message/data/Dice$Key;
public static final field SERIAL_NAME Ljava/lang/String;
public fun <init> (I)V
public synthetic fun <init> (IILkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun appendMiraiCodeTo (Ljava/lang/StringBuilder;)V
public final fun component1 ()I
public final fun copy (I)Lnet/mamoe/mirai/message/data/Dice;
public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/Dice;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Dice;
public fun equals (Ljava/lang/Object;)Z
public fun getId ()I
public fun getName ()Ljava/lang/String;
public final fun getValue ()I
public fun hashCode ()I
public static final fun random ()Lnet/mamoe/mirai/message/data/Dice;
public fun toString ()Ljava/lang/String;
public static final fun write$Self (Lnet/mamoe/mirai/message/data/Dice;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}
public final class net/mamoe/mirai/message/data/Dice$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/Dice$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/Dice;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/message/data/Dice;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Dice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public final fun random ()Lnet/mamoe/mirai/message/data/Dice;
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/EmptyMessageChain : java/util/List, kotlin/jvm/internal/markers/KMappedMarker, net/mamoe/mirai/message/data/MessageChain {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/EmptyMessageChain;
public synthetic fun add (ILjava/lang/Object;)V

View File

@ -76,6 +76,7 @@ Mirai 支持富文本消息。
[`FlashImage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/FlashImage.kt
[`MarketFace`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt
[`MusicShare`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt
[`Dice`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt
[`MessageSource`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt
[`QuoteReply`]: ../mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt
@ -102,7 +103,8 @@ Mirai 支持富文本消息。
| [`MarketFace`] | 商城表情 | `[表情对应的中文名]` | 2.0 |
| [`ForwardMessage`] | 合并转发 | `[转发消息]` | 2.0 *<sup>(1)</sup>* |
| [`SimpleServiceMessage`] | (不稳定)服务消息 | `$content` | 2.0 |
| [`MusicShare`] | 音乐分享 | `[分享]曲名` | 2.1 |
| [`MusicShare`] | 音乐分享 | `[分享]曲名` | 2.1 |
| [`Dice`] | 骰子 | `[骰子:$value]` | 2.5 |
@ -357,6 +359,7 @@ at.serializeToMiraiCode() // 结果为 `[mirai:at:123]`
| [`VipFace`] | `[mirai:vipface:${kind.id},${kind.name},$count]` |
| [`LightApp`] | `[mirai:app:$content]` |
| [`SimpleServiceMessage`] | `[mirai:service:$serviceId,$content]` |
| [`Dice`] | `[mirai:dice:$value]` |
### 由 mirai 码字符串取得 `MessageChain` 实例

View File

@ -141,6 +141,8 @@ private val builtInSerializersModule by lazy {
subclass(FlashImage::class, FlashImage.serializer())
subclass(MusicShare::class, MusicShare.serializer())
subclass(Dice::class, Dice.serializer())
}

View File

@ -124,7 +124,10 @@ private object MiraiCodeParsers : Map<String, MiraiCodeParser> by mapOf(
},
"app" to MiraiCodeParser(Regex("""(.*)""")) { (content) ->
LightApp(content.decodeMiraiCode())
}
},
"dice" to MiraiCodeParser(Regex("""([1-6])""")) { (value) ->
Dice(value.toInt())
},
)
private class MiraiCodeParser(

View File

@ -0,0 +1,67 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE")
@file:JvmMultifileClass
@file:JvmName("MessageUtils")
package net.mamoe.mirai.message.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.safeCast
import org.jetbrains.annotations.Range
import kotlin.random.Random
import kotlin.random.nextInt
/**
* 骰子.
*
* @since 2.5
*/
@Serializable
@SerialName(Dice.SERIAL_NAME)
public data class Dice(
/**
* 骰子的点数. 范围为 1..6
*/
public val value: @Range(from = 1, to = 6) Int
) : MarketFace, CodableMessage {
init {
require(value in 1..6) { "Dice.value must be in 1 and 6 inclusive." }
}
@MiraiExperimentalApi
override val name: String
get() = "[骰子:$value]"
@MiraiExperimentalApi
override val id: Int
get() = 11464
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append("[mirai:dice:").append(value).append(']')
}
override fun toString(): String = "[mirai:dice:$value]"
public companion object Key :
AbstractPolymorphicMessageKey<MarketFace, Dice>(MarketFace, { it.safeCast() }) {
public const val SERIAL_NAME: String = "Dice"
/**
* 创建随机点数的 [骰子][Dice]
*/
@JvmStatic
public fun random(): Dice = Dice(Random.nextInt(1..6))
}
}

View File

@ -15,7 +15,9 @@ import net.mamoe.mirai.utils.safeCast
/**
* 商城表情
*
* 目前不支持直接发送可保存接收到的来自官方客户端的商城表情然后转发.
* [Dice] 可以发送外, 目前不支持直接发送可保存接收到的来自官方客户端的商城表情然后转发.
*
* @see Dice
*/
public interface MarketFace : HummerMessage {
/**

View File

@ -43,5 +43,8 @@ class TestMiraiCode {
+SimpleServiceMessage(1, "[HiHi!!!\\]")
+PlainText(" XE")
}, "[mirai:service:1,\\[HiHi!!!\\\\\\]] XE".deserializeMiraiCode())
assertEquals(buildMessageChain {
+Dice(1)
}, "[mirai:dice:1]".deserializeMiraiCode())
}
}

View File

@ -240,7 +240,9 @@ internal abstract class SendMessageHandler<C : Contact> {
* - ... any others for future
*/
internal suspend fun <C : Contact> SendMessageHandler<C>.transformSpecialMessages(message: Message): MessageChain {
return message.takeSingleContent<ForwardMessage>()?.let { forward ->
suspend fun processForwardMessage(
forward: ForwardMessage
): ForwardMessageInternal {
if (!(message is MessageChain && message.contains(IgnoreLengthCheck))) {
check(forward.nodeList.size <= 200) {
throw MessageTooLargeException(
@ -256,12 +258,20 @@ internal suspend fun <C : Contact> SendMessageHandler<C>.transformSpecialMessage
message = forward.nodeList,
isLong = false,
)
RichMessage.forwardMessage(
return RichMessage.forwardMessage(
resId = resId,
timeSeconds = currentTimeSeconds(),
forwardMessage = forward,
)
}?.toMessageChain() ?: message.toMessageChain()
}
fun processDice(dice: Dice): MarketFaceImpl {
return MarketFaceImpl(dice.toJceStruct())
}
return message.takeSingleContent<ForwardMessage>()?.let { processForwardMessage(it) }?.toMessageChain()
?: message.takeSingleContent<Dice>()?.let { processDice(it) }?.toMessageChain()
?: message.toMessageChain()
}
/**

View File

@ -0,0 +1,93 @@
/*
* Copyright 2019-2021 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.internal.message
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.message.data.Dice
import net.mamoe.mirai.message.data.MarketFace
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
@SerialName(MarketFace.SERIAL_NAME)
@Serializable
internal data class MarketFaceImpl internal constructor(
internal val delegate: ImMsgBody.MarketFace,
) : MarketFace {
override val name: String get() = delegate.faceName.decodeToString()
@Transient
override val id: Int = delegate.tabId
override fun toString() = "[mirai:marketface:$id,$name]"
}
/**
* For refinement
*/
internal class MarketFaceInternal(
@JvmField private val delegate: ImMsgBody.MarketFace,
) : MarketFace, RefinableMessage {
override val name: String get() = delegate.faceName.decodeToString()
override val id: Int get() = delegate.tabId
override suspend fun refine(contact: Contact, context: MessageChain): Message {
delegate.toDiceOrNull()?.let { return it } // TODO: 2021/2/12 add dice origin, maybe rename RichMessageOrigin
return MarketFaceImpl(delegate)
}
override fun toString(): String = "[mirai:marketface:$id,$name]"
}
// From https://github.com/mamoe/mirai/issues/1012
internal fun Dice.toJceStruct(): ImMsgBody.MarketFace {
return ImMsgBody.MarketFace(
faceName = byteArrayOf(91, -23, -86, -80, -27, -83, -112, 93),
itemType = 6,
faceInfo = 1,
faceId = byteArrayOf(
72, 35, -45, -83, -79, 93,
-16, -128, 20, -50, 93, 103,
-106, -73, 110, -31
),
tabId = 11464,
subType = 3,
key = byteArrayOf(52, 48, 57, 101, 50, 97, 54, 57, 98, 49, 54, 57, 49, 56, 102, 57),
mediaType = 0,
imageWidth = 200,
imageHeight = 200,
mobileParam = byteArrayOf(
114, 115, 99, 84, 121, 112, 101,
63, 49, 59, 118, 97, 108, 117,
101, 61,
(47 + value).toByte()
),
pbReserve = byteArrayOf(
10, 6, 8, -56, 1, 16, -56, 1, 64,
1, 88, 0, 98, 9, 35, 48, 48, 48,
48, 48, 48, 48, 48, 106, 9, 35,
48, 48, 48, 48, 48, 48, 48, 48
)
)
}
private fun ImMsgBody.MarketFace.toDiceOrNull(): Dice? {
if (this.tabId != 11464) return null
val value = mobileParam.lastOrNull()?.toInt()?.and(0xff)?.minus(47) ?: 0
if (value in 1..6) {
return Dice(value)
}
return null
}

View File

@ -140,7 +140,7 @@ private object ReceiveMessageTransformer {
element.customFace != null -> decodeCustomFace(element.customFace, builder)
element.face != null -> builder.add(Face(element.face.index))
element.text != null -> decodeText(element.text, builder)
element.marketFace != null -> builder.add(MarketFaceImpl(element.marketFace))
element.marketFace != null -> builder.add(MarketFaceInternal(element.marketFace))
element.lightApp != null -> decodeLightApp(element.lightApp, builder)
element.customElem != null -> decodeCustomElem(element.customElem, builder)
element.commonElem != null -> decodeCommonElem(element.commonElem, builder)

View File

@ -10,14 +10,10 @@
package net.mamoe.mirai.internal.message
import kotlinx.io.core.toByteArray
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.internal.network.protocol.data.proto.HummerCommelem
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.data.Face
import net.mamoe.mirai.message.data.MarketFace
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toByteArray
@ -42,18 +38,4 @@ internal fun Face.toCommData(): ImMsgBody.CommonElem {
businessType = 1
)
}
@SerialName(MarketFace.SERIAL_NAME)
@Serializable
internal data class MarketFaceImpl internal constructor(
internal val delegate: ImMsgBody.MarketFace,
) : MarketFace {
override val name: String get() = delegate.faceName.decodeToString()
@Transient
override val id: Int = delegate.tabId
override fun toString() = "[mirai:marketface:$id,$name]"
}

View File

@ -109,7 +109,8 @@ internal class MessageSerializationTest {
image.toForwardMessage(1L, "test"),
MusicShare(MusicKind.NeteaseCloudMusic, "123", "123", "123", "123", "123", "123"),
RichMessageOrigin(SimpleServiceMessage(1, "content"), "resource id", RichMessageKind.LONG),
ShowImageFlag
ShowImageFlag,
Dice(1),
)
companion object {