Skip to content

Commit 3d667e7

Browse files
committed
feat: rich text parameters
1 parent 406796f commit 3d667e7

10 files changed

Lines changed: 139 additions & 69 deletions

File tree

detekt.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ style:
55
active: false
66
MagicNumber:
77
active: false
8+
MaxLineLength:
9+
maxLineLength: 150

saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/dsl/CommandBuilder.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.ntqqrev.saltify.dsl
22

3+
import org.ntqqrev.milky.IncomingSegment
34
import org.ntqqrev.saltify.annotation.SaltifyDsl
45
import org.ntqqrev.saltify.model.command.CommandError
56
import org.ntqqrev.saltify.model.command.CommandRequirement
@@ -73,15 +74,19 @@ public class SaltifyParameterBuilder(@PublishedApi internal val context: Command
7374
/**
7475
* 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。
7576
*
76-
* @param isGreedy 是否是贪婪参数,即是否包含后面的所有剩余文本
77-
* @param transform 将原始文本转化为目标类型的函数
77+
* @param isGreedy 是否是贪婪参数,即是否包含后面的所有剩余内容
78+
* @param transform 将原始内容转化为目标类型的函数
7879
*/
7980
public inline fun <reified T : Any> from(
8081
name: String,
8182
description: String,
8283
isGreedy: Boolean = false,
83-
noinline transform: (String) -> T?
84-
): CommandParameter<T> = CommandParameter(transform, T::class, name, description, isGreedy).also {
85-
context.parameters.add(it)
86-
}
84+
noinline transform: (IncomingSegment) -> T?
85+
): CommandParameter<T> = CommandParameter(
86+
transform,
87+
T::class,
88+
name,
89+
description,
90+
isGreedy
91+
).also { context.parameters.add(it) }
8792
}

saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/ApplicationExtension.kt

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public inline fun ApplicationContext.regex(
5454
}
5555
}
5656

57-
private val spaceRegex = Regex("\\s+")
57+
private val spaceOrWordRegex = Regex("([^ ]+| +)")
5858

5959
/**
6060
* 注册一个指令。
@@ -72,29 +72,42 @@ public fun ApplicationContext.command(
7272

7373
client.commandRegistry.add(
7474
RegisteredCommand(
75-
name = name,
76-
prefix = prefix,
77-
description = rootDsl.description,
78-
parameters = rootDsl.parameters.toList(),
79-
subCommands = rootDsl.subCommands.map { (subName, subCtx) ->
75+
name,
76+
prefix,
77+
rootDsl.description,
78+
rootDsl.parameters.toList(),
79+
rootDsl.subCommands.map { (subName, subCtx) ->
8080
subCtx.toSubCommand(subName)
8181
},
82-
pluginName = pluginName
82+
pluginName
8383
)
8484
)
8585

8686
return on<Event.MessageReceive> {
87-
val rawText = event.segments.filterIsInstance<IncomingSegment.Text>()
88-
.joinToString("") { it.text }
89-
.trim()
87+
val segments = event.segments.flatMap { segment ->
88+
if (segment is IncomingSegment.Text) {
89+
spaceOrWordRegex.findAll(segment.text)
90+
.mapNotNull { match ->
91+
val str = match.value
92+
93+
val processedStr = if (str.startsWith(" ")) str.drop(1) else str
94+
if (processedStr.isNotEmpty()) {
95+
IncomingSegment.Text(IncomingSegment.Text.Data(processedStr))
96+
} else {
97+
null
98+
}
99+
}.toList()
100+
} else {
101+
listOf(segment)
102+
}
103+
}
104+
val leadingText = (segments[0] as? IncomingSegment.Text)?.text ?: return@on
90105

91-
val tokens = if (rawText.isEmpty()) emptyList() else rawText.split(spaceRegex)
92-
if (tokens.isEmpty() || tokens[0] != "$prefix$name") return@on
106+
if (!leadingText.startsWith("$prefix$name")) return@on
93107

94108
CommandEngine.execute(
95109
rootDsl,
96-
tokens.drop(1),
97-
rawText.removePrefix("$prefix$name").removePrefix(" "),
110+
segments.drop(1),
98111
client,
99112
event,
100113
name
Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
@file:Suppress("TooManyFunctions")
2+
13
package org.ntqqrev.saltify.extension
24

5+
import org.ntqqrev.milky.IncomingSegment
36
import org.ntqqrev.saltify.dsl.SaltifyParameterBuilder
47
import org.ntqqrev.saltify.runtime.command.CommandParameter
58
import org.ntqqrev.saltify.runtime.context.CommandExecutionContext
@@ -8,34 +11,64 @@ import org.ntqqrev.saltify.runtime.context.CommandExecutionContext
811
* 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。
912
*/
1013
public fun SaltifyParameterBuilder.string(name: String, desc: String = ""): CommandParameter<String> =
11-
from(name, desc) { it }
14+
from(name, desc) { (it as? IncomingSegment.Text)?.text }
1215

1316
/**
1417
* 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。
1518
*/
1619
public fun SaltifyParameterBuilder.int(name: String, desc: String = ""): CommandParameter<Int> =
17-
from(name, desc) { it.toIntOrNull() }
20+
from(name, desc) { (it as? IncomingSegment.Text)?.text?.toIntOrNull() }
1821

1922
/**
2023
* 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。
2124
*/
2225
public fun SaltifyParameterBuilder.long(name: String, desc: String = ""): CommandParameter<Long> =
23-
from(name, desc) { it.toLongOrNull() }
26+
from(name, desc) { (it as? IncomingSegment.Text)?.text?.toLongOrNull() }
2427

2528
/**
2629
* 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。
2730
*/
2831
public fun SaltifyParameterBuilder.boolean(name: String, desc: String = ""): CommandParameter<Boolean> =
29-
from(name, desc) { it.toBooleanStrictOrNull() }
32+
from(name, desc) { (it as? IncomingSegment.Text)?.text?.toBooleanStrictOrNull() }
3033

3134
/**
3235
* 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。
3336
*/
3437
public fun SaltifyParameterBuilder.double(name: String, desc: String = ""): CommandParameter<Double> =
35-
from(name, desc) { it.toDoubleOrNull() }
38+
from(name, desc) { (it as? IncomingSegment.Text)?.text?.toDoubleOrNull() }
3639

3740
/**
3841
* 定义一个贪婪字符串参数。该参数会捕获剩余的**所有**文本内容。请搭配 [CommandExecutionContext.value] 使用。
3942
*/
4043
public fun SaltifyParameterBuilder.greedyString(name: String, desc: String = ""): CommandParameter<String> =
41-
from(name, desc, isGreedy = true) { it }
44+
from(name, desc, isGreedy = true) { (it as? IncomingSegment.Text)?.text }
45+
46+
/**
47+
* 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。
48+
*/
49+
public fun SaltifyParameterBuilder.image(name: String, desc: String = ""): CommandParameter<IncomingSegment.Image.Data> =
50+
from(name, desc) { (it as? IncomingSegment.Image)?.data }
51+
52+
/**
53+
* 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。
54+
*/
55+
public fun SaltifyParameterBuilder.mention(name: String, desc: String = ""): CommandParameter<IncomingSegment.Mention.Data> =
56+
from(name, desc) { (it as? IncomingSegment.Mention)?.data }
57+
58+
/**
59+
* 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。
60+
*/
61+
public fun SaltifyParameterBuilder.mentionAll(name: String, desc: String = ""): CommandParameter<Unit> =
62+
from(name, desc) { if (it is IncomingSegment.MentionAll) Unit else null }
63+
64+
/**
65+
* 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。
66+
*/
67+
public fun SaltifyParameterBuilder.face(name: String, desc: String = ""): CommandParameter<IncomingSegment.Face.Data> =
68+
from(name, desc) { (it as? IncomingSegment.Face)?.data }
69+
70+
/**
71+
* 定义一个指令参数。请搭配 [CommandExecutionContext.value] 使用。
72+
*/
73+
public fun SaltifyParameterBuilder.reply(name: String, desc: String = ""): CommandParameter<IncomingSegment.Reply.Data> =
74+
from(name, desc) { (it as? IncomingSegment.Reply)?.data }

saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/extension/IncomingMessageExtension.kt

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,28 @@ package org.ntqqrev.saltify.extension
22

33
import org.ntqqrev.milky.IncomingSegment
44

5+
public val IncomingSegment.plainText: String
6+
get() = when (this) {
7+
is IncomingSegment.Text -> text
8+
is IncomingSegment.Face -> "[表情]"
9+
is IncomingSegment.Image -> "[图片]"
10+
is IncomingSegment.Record -> "[语音]"
11+
is IncomingSegment.Video -> "[视频]"
12+
is IncomingSegment.File -> "[文件]"
13+
is IncomingSegment.Mention -> "@$name"
14+
is IncomingSegment.MentionAll -> "@全体成员"
15+
is IncomingSegment.Reply -> "[回复]"
16+
is IncomingSegment.Forward -> "[合并转发]"
17+
is IncomingSegment.MarketFace -> "[商城表情]"
18+
is IncomingSegment.LightApp -> "[小程序]"
19+
is IncomingSegment.Xml -> "[XML消息]"
20+
}
21+
522
/**
623
* 将当前 `List<IncomingSegment>` 强制转换为文本形式,非文本形式的 segments 将以预定义文本占位。
724
*/
825
public val List<IncomingSegment>.plainText: String
9-
get() = joinToString("") { segment ->
10-
when (segment) {
11-
is IncomingSegment.Text -> segment.text
12-
is IncomingSegment.Face -> "[表情]"
13-
is IncomingSegment.Image -> "[图片]"
14-
is IncomingSegment.Record -> "[语音]"
15-
is IncomingSegment.Video -> "[视频]"
16-
is IncomingSegment.File -> "[文件]"
17-
is IncomingSegment.Mention -> "@${segment.name}"
18-
is IncomingSegment.MentionAll -> "@全体成员"
19-
is IncomingSegment.Reply -> "[回复]"
20-
is IncomingSegment.Forward -> "[合并转发]"
21-
is IncomingSegment.MarketFace -> "[商城表情]"
22-
is IncomingSegment.LightApp -> "[小程序]"
23-
is IncomingSegment.Xml -> "[XML消息]"
24-
}
25-
}
26+
get() = joinToString("") { it.plainText }
2627

2728
/**
2829
* 判断当前 `List<IncomingSegment>` 是否由指定的 `IncomingSegment` 组成。

saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/internal/engine/CommandEngine.kt

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package org.ntqqrev.saltify.internal.engine
44

55
import org.ntqqrev.milky.Event
66
import org.ntqqrev.milky.IncomingMessage
7+
import org.ntqqrev.milky.IncomingSegment
78
import org.ntqqrev.saltify.SaltifyApplication
89
import org.ntqqrev.saltify.dsl.CommandBuilder
910
import org.ntqqrev.saltify.model.command.CommandError
@@ -16,8 +17,7 @@ import kotlin.time.Clock
1617
internal object CommandEngine {
1718
suspend fun execute(
1819
dsl: CommandBuilder,
19-
tokens: List<String>,
20-
rawParameters: String,
20+
segments: List<IncomingSegment>,
2121
client: SaltifyApplication,
2222
event: Event.MessageReceive,
2323
name: String
@@ -26,21 +26,20 @@ internal object CommandEngine {
2626
val execution = CommandExecutionContext(client, event, argumentMap, name)
2727

2828
if (!checkRequirements(dsl, execution)) return
29-
if (tokens.isNotEmpty()) {
30-
val subName = tokens[0]
29+
if (segments.isNotEmpty()) {
30+
val subName = (segments[0] as? IncomingSegment.Text)?.text
3131
val subCommand = dsl.subCommands.find { it.first == subName }
3232

3333
if (subCommand != null) return execute(
34-
dsl = subCommand.second,
35-
tokens = tokens.drop(1),
36-
rawParameters = rawParameters.removePrefix(subCommand.first).removePrefix(" "),
37-
client = client,
38-
event = event,
39-
name = "$name $subName"
34+
subCommand.second,
35+
segments.drop(1),
36+
client,
37+
event,
38+
"$name $subName"
4039
)
4140
}
4241

43-
val error = parseParameters(dsl, tokens, rawParameters, argumentMap)
42+
val error = parseParameters(dsl, segments, argumentMap)
4443
if (error != null) {
4544
dsl.failureBlock?.invoke(execution, error)
4645
return
@@ -60,25 +59,28 @@ internal object CommandEngine {
6059
*/
6160
private fun parseParameters(
6261
dsl: CommandBuilder,
63-
tokens: List<String>,
64-
rawParameters: String,
62+
segments: List<IncomingSegment>,
6563
argumentMap: MutableMap<CommandParameter<*>, ParameterParseResult<Any>>
6664
): CommandError? {
67-
val currentTokens = tokens.toMutableList()
68-
var currentRaw = rawParameters
65+
val currentTokens = segments.toMutableList()
6966

7067
for (param in dsl.parameters) {
7168
val result: ParameterParseResult<Any> = when {
7269
currentTokens.isEmpty() -> ParameterParseResult.MissingParam
7370
param.isGreedy -> {
74-
val greedyValue = currentRaw
71+
val greedyValue = currentTokens.joinToString(" ") { segment ->
72+
if (segment is IncomingSegment.Text) {
73+
segment.text
74+
} else {
75+
segment.toString()
76+
}
77+
}
78+
7579
currentTokens.clear()
76-
currentRaw = ""
7780
ParameterParseResult.Success(greedyValue)
7881
}
7982
else -> {
8083
val rawValue = currentTokens.removeFirst()
81-
currentRaw = currentRaw.trimStart().removePrefix(rawValue).removePrefix(" ")
8284

8385
param.transform(rawValue)?.let { ParameterParseResult.Success(it) }
8486
?: ParameterParseResult.InvalidParam(rawValue)

saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/model/command/CommandError.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.ntqqrev.saltify.model.command
22

3+
import org.ntqqrev.milky.IncomingSegment
4+
import org.ntqqrev.saltify.extension.plainText
35
import org.ntqqrev.saltify.runtime.command.CommandParameter
46

57
/**
@@ -13,13 +15,13 @@ public sealed class CommandError {
1315
get() = "缺少必要的参数: ${parameter.name}"
1416
}
1517

16-
public data class InvalidParam(val parameter: CommandParameter<*>, val token: String) : CommandError() {
18+
public data class InvalidParam(val parameter: CommandParameter<*>, val segment: IncomingSegment) : CommandError() {
1719
override val message: String
18-
get() = "参数 ${parameter.name} 无效: \"$token\""
20+
get() = "参数 ${parameter.name} 无效: \"${segment.plainText}\""
1921
}
2022

21-
public data class TooManyArguments(val extraTokens: List<String>) : CommandError() {
23+
public data class TooManyArguments(val extraSegments: List<IncomingSegment>) : CommandError() {
2224
override val message: String
23-
get() = "提供参数过多: ${extraTokens.joinToString(" ")}"
25+
get() = "提供参数过多: ${extraSegments.joinToString(" ") { it.plainText }}"
2426
}
2527
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.ntqqrev.saltify.model.command
22

3+
import org.ntqqrev.milky.IncomingSegment
4+
35
internal sealed class ParameterParseResult<out T : Any> {
46
data class Success<T : Any>(val value: T) : ParameterParseResult<T>()
5-
data class InvalidParam(val rawValue: String) : ParameterParseResult<Unit>()
7+
data class InvalidParam(val rawValue: IncomingSegment) : ParameterParseResult<Unit>()
68
object MissingParam : ParameterParseResult<Unit>()
79
}

saltify-core/src/commonMain/kotlin/org/ntqqrev/saltify/runtime/command/CommandParameter.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package org.ntqqrev.saltify.runtime.command
22

3+
import org.ntqqrev.milky.IncomingSegment
34
import kotlin.reflect.KClass
45

56
/**
67
* 指令参数
78
*/
89
public class CommandParameter<T : Any>(
9-
internal val transform: (String) -> T?,
10+
internal val transform: (IncomingSegment) -> T?,
1011
public val type: KClass<T>,
1112
public val name: String,
1213
public val description: String = "",

saltify-core/src/jvmTest/kotlin/org/ntqqrev/saltify/PluginTest.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ class PluginTest {
1616
fun test(): Unit = runBlocking {
1717
val client = SaltifyApplication {
1818
connection {
19-
baseUrl = "http://aliorpse.tech:3010"
20-
accessToken = "Ali081023"
19+
baseUrl = "http://localhost:3010"
2120

2221
events {
2322
type = EventConnectionType.WebSocket
@@ -159,5 +158,15 @@ class PluginTest {
159158
respond("Order #${id.value} created\nnote:${note.value}")
160159
}
161160
}
161+
162+
// rich text param test
163+
command("rich") {
164+
val mention = parameter.mention("mention")
165+
val image = parameter.image("image")
166+
167+
onExecute {
168+
respond("You mentioned ${mention.value.name}, and sent an image: ${image.value.tempUrl}")
169+
}
170+
}
162171
}
163172
}

0 commit comments

Comments
 (0)