diff --git a/binary-compatibility-validator/api/binary-compatibility-validator.api b/binary-compatibility-validator/api/binary-compatibility-validator.api index aa4beb76c..b4fa01302 100644 --- a/binary-compatibility-validator/api/binary-compatibility-validator.api +++ b/binary-compatibility-validator/api/binary-compatibility-validator.api @@ -4706,12 +4706,13 @@ public final class net/mamoe/mirai/message/data/MusicKind : java/lang/Enum { public static fun values ()[Lnet/mamoe/mirai/message/data/MusicKind; } -public final class net/mamoe/mirai/message/data/MusicShare : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent { +public final class net/mamoe/mirai/message/data/MusicShare : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/MusicShare$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun (ILnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun (Lnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V public fun (Lnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public fun appendMiraiCodeTo (Ljava/lang/StringBuilder;)V public final fun component1 ()Lnet/mamoe/mirai/message/data/MusicKind; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; diff --git a/mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt b/mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt index 39f226255..cfe2a0abe 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt @@ -25,13 +25,7 @@ internal fun String.parseMiraiCodeImpl(contact: Contact?): MessageChain = buildM add(PlainText(origin.decodeMiraiCode())) return@forEachMiraiCode } - parser.argsRegex.matchEntire(args) - ?.destructured - ?.let { - parser.runCatching { - contact.mapper(it) - }.getOrNull() - } + parser.parse(contact, args) ?.let(::add) ?: add(PlainText(origin.decodeMiraiCode())) } @@ -128,12 +122,76 @@ private object MiraiCodeParsers : Map by mapOf( "dice" to MiraiCodeParser(Regex("""([1-6])""")) { (value) -> Dice(value.toInt()) }, + "musicshare" to MiraiCodeParser.DynamicParser(7) { args -> + val (kind, title, summary, jumpUrl, pictureUrl) = args + val musicUrl = args[5] + val brief = args[6] + + MusicShare(MusicKind.valueOf(kind), title, summary, jumpUrl, pictureUrl, musicUrl, brief) + }, ) -private class MiraiCodeParser( - val argsRegex: Regex, - val mapper: Contact?.(MatchResult.Destructured) -> Message? -) + +// Visitable for test +internal sealed class MiraiCodeParser { + abstract fun parse(contact: Contact?, args: String): Message? + class RegexParser( + private val argsRegex: Regex, + private val mapper: Contact?.(MatchResult.Destructured) -> Message? + ) : MiraiCodeParser() { + override fun parse(contact: Contact?, args: String): Message? = + argsRegex.matchEntire(args) + ?.destructured + ?.let { + runCatching { + contact.mapper(it) + }.getOrNull() + } + } + + class DynamicParser( + private val minArgs: Int, + private val maxArgs: Int = minArgs, + private val parser: (Contact?.(args: Array) -> Message?), + ) : MiraiCodeParser() { + override fun parse(contact: Contact?, args: String): Message? { + val ranges = mutableListOf() + if (args.isNotEmpty()) { + var begin = 0 + var pos = 0 + val len = args.length + while (pos < len) { + when (args[pos]) { + '\\' -> pos += 2 + ',' -> { + ranges.add(begin..pos) + pos++ + begin = pos + } + else -> pos++ + } + } + ranges.add(begin..len) + } + if (ranges.size < minArgs) return null + if (ranges.size > maxArgs) return null + @Suppress("RemoveExplicitTypeArguments") + val args0 = Array(ranges.size) { index -> + val range = ranges[index] + args.substring(range.first, range.last).decodeMiraiCode() + } + runCatching { + return parser(contact, args0) + } + return null + } + } +} + +private fun MiraiCodeParser( + argsRegex: Regex, + mapper: Contact?.(MatchResult.Destructured) -> Message? +): MiraiCodeParser = MiraiCodeParser.RegexParser(argsRegex, mapper) internal fun StringBuilder.appendStringAsMiraiCode(value: String): StringBuilder = apply { value.forEach { char -> diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt index fe63cc520..e80a125ae 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt @@ -13,6 +13,8 @@ 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.message.code.internal.appendStringAsMiraiCode import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.safeCast @@ -53,7 +55,7 @@ public data class MusicShare( * 在消息列表显示 */ public val brief: String, -) : MessageContent, ConstrainSingle { +) : MessageContent, ConstrainSingle, CodableMessage { public constructor( /** @@ -88,6 +90,19 @@ public data class MusicShare( override fun contentToString(): String = brief.takeIf { it.isNotBlank() } ?: "[分享]$title" // empty content is not accepted by `sendMessage` + override fun appendMiraiCodeTo(builder: StringBuilder) { + builder.append("[mirai:musicshare:") + .append(kind.name) + .append(',').appendStringAsMiraiCode(title) + .append(',').appendStringAsMiraiCode(summary) + .append(',').appendStringAsMiraiCode(jumpUrl) + .append(',').appendStringAsMiraiCode(pictureUrl) + .append(',').appendStringAsMiraiCode(musicUrl) + .append(',').appendStringAsMiraiCode(brief) + .append(']') + } + + // MusicShare(type=NeteaseCloudMusic, title='ファッション', summary='rinahamu/Yunomi', brief='', url='http://music.163.com/song/1338728297/?userid=324076307', pictureUrl='http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg', musicUrl='http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307') /** diff --git a/mirai-core-api/src/commonTest/kotlin/message/code/TestMiraiCode.kt b/mirai-core-api/src/commonTest/kotlin/message/code/TestMiraiCode.kt index 6b0dcc4b3..a62b48daa 100644 --- a/mirai-core-api/src/commonTest/kotlin/message/code/TestMiraiCode.kt +++ b/mirai-core-api/src/commonTest/kotlin/message/code/TestMiraiCode.kt @@ -10,11 +10,27 @@ package net.mamoe.mirai.message.code import net.mamoe.mirai.message.code.MiraiCode.deserializeMiraiCode +import net.mamoe.mirai.message.code.internal.MiraiCodeParser import net.mamoe.mirai.message.data.* import org.junit.jupiter.api.Test import kotlin.test.assertEquals +import kotlin.test.assertNotNull class TestMiraiCode { + @Test + fun testDynamicMiraiCodeParser() { + fun runTest(args: Int, code: String, parse: (args: Array) -> Unit) { + val response = MiraiCodeParser.DynamicParser(args) { args0 -> parse(args0); AtAll }.parse(null, code) + assertNotNull(response, "Parser not invoked") + } + runTest(3, "test,\\,test,\\,\\,test") { (arg1, arg2, arg3) -> + assertEquals("test", arg1) + assertEquals(",test", arg2) + assertEquals(",,test", arg3) + } + runTest(2, ",") {} + } + @Test fun testCodes() { assertEquals(AtAll.toMessageChain(), "[mirai:atall]".deserializeMiraiCode()) @@ -46,5 +62,16 @@ class TestMiraiCode { assertEquals(buildMessageChain { +Dice(1) }, "[mirai:dice:1]".deserializeMiraiCode()) + + val musicShare = MusicShare( + kind = MusicKind.NeteaseCloudMusic, + title = "ファッション", + summary = "rinahamu/Yunomi", + jumpUrl = "http://music.163.com/song/1338728297/?userid=324076307", + pictureUrl = "http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg", + musicUrl = "http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307", + brief = "", + ) + assertEquals(musicShare.toMessageChain(), musicShare.serializeToMiraiCode().deserializeMiraiCode()) } } \ No newline at end of file