Skip to content

Commit 3693b86

Browse files
authored
Merge pull request #253 from simple-robot/dev/improve-sendforwardmsg-APIs
改进 send forward msg 相关API
2 parents 215d726 + cb00630 commit 3693b86

File tree

6 files changed

+489
-30
lines changed

6 files changed

+489
-30
lines changed

simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/api/GetMsgApi.kt

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
/*
2-
* Copyright (c) 2024. ForteScarlet.
2+
* Copyright (c) 2024-2025. ForteScarlet.
33
*
4-
* This file is part of simbot-component-onebot.
4+
* Project https://github.com/simple-robot/simbot-component-onebot
5+
56
*
6-
* simbot-component-onebot is free software: you can redistribute it and/or modify it under the terms
7-
* of the GNU Lesser General Public License as published by the Free Software Foundation,
8-
* either version 3 of the License, or (at your option) any later version.
7+
* This project and this file are part of the Simple Robot Library (Alias: simple-robot, simbot, etc.).
98
*
10-
* simbot-component-onebot is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11-
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12-
* See the GNU Lesser General Public License for more details.
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Lesser General Public License as published by
11+
* the Free Software Foundation, either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* Lesser GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the Lesser GNU General Public License
20+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1321
*
14-
* You should have received a copy of the GNU Lesser General Public License along with simbot-component-onebot.
15-
* If not, see <https://www.gnu.org/licenses/>.
1622
*/
1723

1824
package love.forte.simbot.component.onebot.v11.core.api
@@ -87,9 +93,9 @@ public data class GetMsgResult @ApiResultConstructor constructor(
8793
@SerialName("message_type")
8894
public val messageType: String,
8995
@SerialName("message_id")
90-
public val messageId: IntID,
96+
public val messageId: IntID, // TODO to LongID?
9197
@SerialName("real_id")
92-
public val realId: IntID,
98+
public val realId: IntID, // TODO to LongID?
9399
@property:ExperimentalSimbotAPI
94100
public val sender: JsonObject, // Any = TODO("sender?"),
95101
public val message: List<OneBotMessageSegment> = emptyList(), // Any = ("message?"),

simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/api/SendMsgApi.kt

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
/*
2-
* Copyright (c) 2024. ForteScarlet.
2+
* Copyright (c) 2024-2025. ForteScarlet.
33
*
4-
* This file is part of simbot-component-onebot.
4+
* Project https://github.com/simple-robot/simbot-component-onebot
5+
56
*
6-
* simbot-component-onebot is free software: you can redistribute it and/or modify it under the terms
7-
* of the GNU Lesser General Public License as published by the Free Software Foundation,
8-
* either version 3 of the License, or (at your option) any later version.
7+
* This project and this file are part of the Simple Robot Library (Alias: simple-robot, simbot, etc.).
98
*
10-
* simbot-component-onebot is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11-
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12-
* See the GNU Lesser General Public License for more details.
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Lesser General Public License as published by
11+
* the Free Software Foundation, either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* Lesser GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the Lesser GNU General Public License
20+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1321
*
14-
* You should have received a copy of the GNU Lesser General Public License along with simbot-component-onebot.
15-
* If not, see <https://www.gnu.org/licenses/>.
1622
*/
1723

1824
package love.forte.simbot.component.onebot.v11.core.api
@@ -144,5 +150,5 @@ public class SendMsgApi private constructor(
144150
@Serializable
145151
public data class SendMsgResult @ApiResultConstructor constructor(
146152
@SerialName("message_id")
147-
public val messageId: IntID,
153+
public val messageId: IntID, // TODO to LongID?
148154
)

simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/api/nonstandard/SendGroupForwardMsgApi.kt

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ import kotlinx.serialization.DeserializationStrategy
2727
import kotlinx.serialization.SerialName
2828
import kotlinx.serialization.Serializable
2929
import love.forte.simbot.common.id.ID
30-
import love.forte.simbot.common.id.IntID
30+
import love.forte.simbot.common.id.LongID
31+
import love.forte.simbot.common.id.NumericalID
3132
import love.forte.simbot.common.id.literal
3233
import love.forte.simbot.component.onebot.common.annotations.ApiResultConstructor
3334
import love.forte.simbot.component.onebot.v11.core.api.OneBotApi
3435
import love.forte.simbot.component.onebot.v11.core.api.OneBotApiResult
3536
import love.forte.simbot.component.onebot.v11.message.segment.OneBotForwardNode
37+
import love.forte.simbot.component.onebot.v11.message.segment.OneBotMessageSegment
3638
import kotlin.jvm.JvmStatic
3739

3840
/**
@@ -69,7 +71,12 @@ public class SendGroupForwardMsgApi private constructor(
6971
public fun create(
7072
groupId: ID,
7173
messages: List<OneBotForwardNode>,
72-
): SendGroupForwardMsgApi = SendGroupForwardMsgApi(Body(groupId.literal, messages))
74+
): SendGroupForwardMsgApi = SendGroupForwardMsgApi(
75+
Body(
76+
groupId.literal,
77+
messages
78+
)
79+
)
7380
}
7481

7582
/**
@@ -80,7 +87,10 @@ public class SendGroupForwardMsgApi private constructor(
8087
internal data class Body(
8188
@SerialName("group_id")
8289
val groupId: String,
83-
val messages: List<OneBotForwardNode>,
90+
/**
91+
* node 元素列表。使用 [OneBotMessageSegment] 为了确保序列化使用多态。
92+
*/
93+
val messages: List<OneBotMessageSegment>,
8494
)
8595
}
8696

@@ -94,7 +104,10 @@ public class SendGroupForwardMsgApi private constructor(
94104
@OneBotNonStandardApi
95105
public data class SendGroupForwardMsgResult @ApiResultConstructor constructor(
96106
@SerialName("message_id")
97-
public val messageId: IntID,
107+
private val longMessageId: LongID,
98108
@SerialName("forward_id")
99109
public val forwardId: ID
100-
)
110+
) {
111+
public val messageId: NumericalID
112+
get() = longMessageId
113+
}

simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/api/nonstandard/SendPrivateForwardMsgApi.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import love.forte.simbot.component.onebot.v11.core.api.OneBotApi
3232
import love.forte.simbot.component.onebot.v11.core.api.OneBotApiResult
3333
import love.forte.simbot.component.onebot.v11.core.api.SendMsgResult
3434
import love.forte.simbot.component.onebot.v11.message.segment.OneBotForwardNode
35+
import love.forte.simbot.component.onebot.v11.message.segment.OneBotMessageSegment
3536
import kotlin.jvm.JvmStatic
3637

3738
/**
@@ -67,7 +68,12 @@ public class SendPrivateForwardMsgApi private constructor(
6768
public fun create(
6869
userId: ID,
6970
messages: List<OneBotForwardNode>,
70-
): SendPrivateForwardMsgApi = SendPrivateForwardMsgApi(Body(userId.literal, messages))
71+
): SendPrivateForwardMsgApi = SendPrivateForwardMsgApi(
72+
Body(
73+
userId.literal,
74+
messages
75+
)
76+
)
7177
}
7278

7379
/**
@@ -78,6 +84,9 @@ public class SendPrivateForwardMsgApi private constructor(
7884
internal data class Body(
7985
@SerialName("user_id")
8086
val userId: String,
81-
val messages: List<OneBotForwardNode>,
87+
/**
88+
* node 元素列表。使用 [OneBotMessageSegment] 为了确保序列化使用多态。
89+
*/
90+
val messages: List<OneBotMessageSegment>,
8291
)
8392
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/*
2+
* Copyright (c) 2025. ForteScarlet.
3+
*
4+
* Project https://github.com/simple-robot/simbot-component-onebot
5+
6+
*
7+
* This project and this file are part of the Simple Robot Library (Alias: simple-robot, simbot, etc.).
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Lesser General Public License as published by
11+
* the Free Software Foundation, either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* Lesser GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the Lesser GNU General Public License
20+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
21+
*
22+
*/
23+
24+
package love.forte.simbot.component.onebot.v11.core.api
25+
26+
import io.ktor.client.*
27+
import io.ktor.client.engine.mock.*
28+
import kotlinx.coroutines.test.runTest
29+
import kotlinx.serialization.json.jsonArray
30+
import kotlinx.serialization.json.jsonObject
31+
import kotlinx.serialization.json.jsonPrimitive
32+
import love.forte.simbot.common.id.IntID.Companion.ID
33+
import love.forte.simbot.common.id.literal
34+
import love.forte.simbot.component.onebot.v11.core.OneBot11
35+
import love.forte.simbot.component.onebot.v11.core.api.nonstandard.OneBotNonStandardApi
36+
import love.forte.simbot.component.onebot.v11.core.api.nonstandard.SendGroupForwardMsgApi
37+
import love.forte.simbot.component.onebot.v11.message.segment.OneBotForwardNode
38+
import love.forte.simbot.component.onebot.v11.message.segment.OneBotText
39+
import kotlin.test.Test
40+
import kotlin.test.assertEquals
41+
import kotlin.test.assertNotNull
42+
43+
/**
44+
* 测试 API [SendGroupForwardMsgApi]
45+
* @author ForteScarlet
46+
*/
47+
@OptIn(OneBotNonStandardApi::class)
48+
class SendGroupForwardMsgTests {
49+
50+
private val json = OneBot11.DefaultJson
51+
52+
/**
53+
* 测试 llonebot 供应商的群聊合并转发消息
54+
*
55+
* 参考 .local/send_group_forward_msg_api_llonebot.md 的实现
56+
*/
57+
@Test
58+
fun testLLOneBotApi() = runTest {
59+
val groupId = 12345.ID
60+
val userId = 379450326.ID
61+
val nickname = "测试昵称"
62+
63+
// 创建一个消息节点
64+
val node = OneBotForwardNode.create(
65+
userId = userId,
66+
nickname = nickname,
67+
content = listOf(OneBotText.create("测试消息"))
68+
)
69+
70+
// 创建 API
71+
val api = SendGroupForwardMsgApi.create(groupId, listOf(node))
72+
73+
val mockEngine = MockEngine { request ->
74+
// 验证请求 URL
75+
assertEquals("send_group_forward_msg", request.url.pathSegments.last())
76+
77+
// 解析请求体,验证序列化结果
78+
val requestBody = request.body.toByteArray().decodeToString()
79+
println("requestBody: $requestBody")
80+
81+
val requestJson = json.parseToJsonElement(requestBody).jsonObject
82+
83+
// 验证 group_id
84+
val groupIdValue = requestJson["group_id"]?.jsonPrimitive?.content
85+
assertEquals(groupId.toString(), groupIdValue)
86+
87+
// 验证 messages 结构
88+
val messages = requestJson["messages"]?.jsonArray
89+
assertNotNull(messages)
90+
assertEquals(1, messages.size)
91+
92+
val nodeJson = messages[0].jsonObject
93+
assertEquals("node", nodeJson["type"]?.jsonPrimitive?.content)
94+
95+
val nodeData = nodeJson["data"]?.jsonObject
96+
assertNotNull(nodeData)
97+
98+
val nodeContent = nodeData["content"]?.jsonArray
99+
assertNotNull(nodeContent)
100+
assertEquals(1, nodeContent.size)
101+
102+
val textSegment = nodeContent[0].jsonObject
103+
assertEquals("text", textSegment["type"]?.jsonPrimitive?.content)
104+
assertEquals("测试消息", textSegment["data"]?.jsonObject?.get("text")?.jsonPrimitive?.content)
105+
106+
// 返回 llonebot 格式的响应
107+
respondOk(
108+
"""
109+
{
110+
"status": "ok",
111+
"retcode": 0,
112+
"data": {
113+
"message_id": 2026505362,
114+
"forward_id": "zUfJpEhzJgXxJID2cIwUoiRk7dMLSgnbhwb8yPrPz8iK6IsBn2uUQArcosp4WrNH"
115+
},
116+
"message": "",
117+
"wording": ""
118+
}
119+
""".trimIndent()
120+
)
121+
}
122+
123+
val client = HttpClient(mockEngine)
124+
125+
// 执行请求并验证结果
126+
val result = api.requestData(client, "http://127.0.0.1:8080/")
127+
128+
assertEquals(2026505362L, result.messageId.toLong())
129+
assertEquals("zUfJpEhzJgXxJID2cIwUoiRk7dMLSgnbhwb8yPrPz8iK6IsBn2uUQArcosp4WrNH", result.forwardId.literal)
130+
}
131+
132+
/**
133+
* 测试 language-onebot 供应商的群聊合并转发消息
134+
*
135+
* 参考 .local/send_group_forward_msg_api_languageonebot.md 的实现
136+
*/
137+
@Test
138+
fun testLanguageOneBotApi() = runTest {
139+
val groupId = 12345.ID
140+
val userId = 379450326.ID
141+
val nickname = "测试昵称"
142+
143+
// 创建一个消息节点
144+
val node = OneBotForwardNode.create(
145+
userId = userId,
146+
nickname = nickname,
147+
content = listOf(OneBotText.create("测试消息"))
148+
)
149+
150+
// 创建 API
151+
val api = SendGroupForwardMsgApi.create(groupId, listOf(node))
152+
153+
val mockEngine = MockEngine { request ->
154+
// 验证请求 URL
155+
assertEquals("send_group_forward_msg", request.url.pathSegments.last())
156+
157+
// 解析请求体,验证序列化结果
158+
val requestBody = request.body.toByteArray().decodeToString()
159+
println("requestBody: $requestBody")
160+
161+
val requestJson = json.parseToJsonElement(requestBody).jsonObject
162+
163+
164+
// 验证 group_id
165+
val groupIdValue = requestJson["group_id"]?.jsonPrimitive?.content
166+
assertEquals(groupId.toString(), groupIdValue)
167+
168+
// 验证 messages 结构
169+
val messages = requestJson["messages"]?.jsonArray
170+
assertNotNull(messages)
171+
assertEquals(1, messages.size)
172+
173+
val nodeJson = messages[0].jsonObject
174+
assertEquals("node", nodeJson["type"]?.jsonPrimitive?.content)
175+
176+
val nodeData = nodeJson["data"]?.jsonObject
177+
assertNotNull(nodeData)
178+
179+
// language-onebot 使用 user_id 和 nickname
180+
val userIdValue = nodeData["user_id"]?.jsonPrimitive?.content
181+
assertEquals(userId.toString(), userIdValue)
182+
183+
val nicknameValue = nodeData["nickname"]?.jsonPrimitive?.content
184+
assertEquals(nickname, nicknameValue)
185+
186+
val nodeContent = nodeData["content"]?.jsonArray
187+
assertNotNull(nodeContent)
188+
assertEquals(1, nodeContent.size)
189+
190+
val textSegment = nodeContent[0].jsonObject
191+
assertEquals("text", textSegment["type"]?.jsonPrimitive?.content)
192+
assertEquals("测试消息", textSegment["data"]?.jsonObject?.get("text")?.jsonPrimitive?.content)
193+
194+
// 返回 language-onebot 格式的响应
195+
respondOk(
196+
"""
197+
{
198+
"status": "ok",
199+
"retcode": 0,
200+
"data": {
201+
"message_id": 1234567890,
202+
"forward_id": "abcdefghijklmnopqrstuvwxyz"
203+
}
204+
}
205+
""".trimIndent()
206+
)
207+
}
208+
209+
val client = HttpClient(mockEngine)
210+
211+
// 执行请求并验证结果
212+
val result = api.requestData(client, "http://127.0.0.1:8080/")
213+
214+
assertEquals(1234567890L, result.messageId.toLong())
215+
assertEquals("abcdefghijklmnopqrstuvwxyz", result.forwardId.literal)
216+
}
217+
}

0 commit comments

Comments
 (0)