Skip to content

Commit 5d36e13

Browse files
authored
Merge pull request #288 from simple-robot/dev/support-channel-move-user-API
支持频道之间移动用户api
2 parents 5afc99d + 9ebbe47 commit 5d36e13

File tree

3 files changed

+390
-0
lines changed

3 files changed

+390
-0
lines changed

simbot-component-kook-api/api/simbot-component-kook-api.api

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,20 @@ public final class love/forte/simbot/kook/api/channel/ChannelKickoutApi$Factory
517517
public final fun create (Ljava/lang/String;Ljava/lang/String;)Llove/forte/simbot/kook/api/channel/ChannelKickoutApi;
518518
}
519519

520+
public final class love/forte/simbot/kook/api/channel/ChannelMoveUserApi : love/forte/simbot/kook/api/KookPostApi {
521+
public static final field Factory Llove/forte/simbot/kook/api/channel/ChannelMoveUserApi$Factory;
522+
public synthetic fun <init> (Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
523+
public static final fun create (Ljava/lang/String;Ljava/util/List;)Llove/forte/simbot/kook/api/channel/ChannelMoveUserApi;
524+
public static final fun create (Ljava/lang/String;[Ljava/lang/String;)Llove/forte/simbot/kook/api/channel/ChannelMoveUserApi;
525+
public fun getBody ()Ljava/lang/Object;
526+
public fun getResultDeserializationStrategy ()Lkotlinx/serialization/DeserializationStrategy;
527+
}
528+
529+
public final class love/forte/simbot/kook/api/channel/ChannelMoveUserApi$Factory {
530+
public final fun create (Ljava/lang/String;Ljava/util/List;)Llove/forte/simbot/kook/api/channel/ChannelMoveUserApi;
531+
public final fun create (Ljava/lang/String;[Ljava/lang/String;)Llove/forte/simbot/kook/api/channel/ChannelMoveUserApi;
532+
}
533+
520534
public final class love/forte/simbot/kook/api/channel/ChannelView {
521535
public static final field Companion Llove/forte/simbot/kook/api/channel/ChannelView$Companion;
522536
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIZIZLjava/lang/String;Ljava/util/List;ILjava/util/List;)V
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2023-2025. ForteScarlet.
3+
*
4+
* This file is part of simbot-component-kook.
5+
*
6+
* simbot-component-kook is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Lesser General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* simbot-component-kook is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with simbot-component-kook,
18+
* If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package love.forte.simbot.kook.api.channel
22+
23+
import kotlinx.serialization.DeserializationStrategy
24+
import kotlinx.serialization.SerialName
25+
import kotlinx.serialization.Serializable
26+
import kotlinx.serialization.builtins.ListSerializer
27+
import kotlinx.serialization.json.JsonElement
28+
import love.forte.simbot.kook.api.KookPostApi
29+
import kotlin.jvm.JvmStatic
30+
31+
/**
32+
* [语音频道之间移动用户](https://developer.kookapp.cn/doc/http/channel#语音频道之间移动用户)
33+
*
34+
* 语音频道之间移动用户,只能在语音频道之间移动,用户也必须在其他语音频道在线才能够移动到目标频道。
35+
*
36+
*
37+
* @author ForteScarlet
38+
* @since 4.2.0
39+
*/
40+
public class ChannelMoveUserApi private constructor(
41+
targetId: String,
42+
userIds: List<String>
43+
) : KookPostApi<List<JsonElement>>() {
44+
45+
public companion object Factory {
46+
private val PATH = ApiPath.create("channel", "move-user")
47+
48+
/**
49+
* 构造 [ChannelMoveUserApi].
50+
*
51+
* @param targetId 目标频道 id,需要是语音频道
52+
* @param userIds 用户 id 的数组
53+
*/
54+
@JvmStatic
55+
public fun create(targetId: String, userIds: List<String>): ChannelMoveUserApi =
56+
ChannelMoveUserApi(targetId, userIds)
57+
58+
/**
59+
* 构造 [ChannelMoveUserApi].
60+
*
61+
* @param targetId 目标频道 id,需要是语音频道
62+
* @param userIds 用户 id 的数组
63+
*/
64+
@JvmStatic
65+
public fun create(targetId: String, vararg userIds: String): ChannelMoveUserApi =
66+
ChannelMoveUserApi(targetId, userIds.asList())
67+
}
68+
69+
override val resultDeserializationStrategy: DeserializationStrategy<List<JsonElement>>
70+
get() = ListSerializer(JsonElement.serializer())
71+
72+
override val apiPath: ApiPath
73+
get() = PATH
74+
75+
override val body: Any = Body(targetId, userIds)
76+
77+
@Serializable
78+
private data class Body(
79+
@SerialName("target_id")
80+
val targetId: String,
81+
@SerialName("user_ids")
82+
val userIds: List<String>
83+
)
84+
}
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
/*
2+
* Copyright (c) 2023-2025. ForteScarlet.
3+
*
4+
* This file is part of simbot-component-kook.
5+
*
6+
* simbot-component-kook is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Lesser General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* simbot-component-kook is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with simbot-component-kook,
18+
* If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package love.forte.simbot.kook.api.channel
22+
23+
import io.ktor.client.*
24+
import io.ktor.client.engine.mock.*
25+
import io.ktor.http.*
26+
import kotlinx.coroutines.test.runTest
27+
import love.forte.simbot.kook.api.requestData
28+
import love.forte.simbot.kook.api.requestResult
29+
import kotlin.test.Test
30+
import kotlin.test.assertEquals
31+
import kotlin.test.assertNotNull
32+
import kotlin.test.assertTrue
33+
34+
/**
35+
* Tests for [ChannelMoveUserApi] focusing on API structure and serialization.
36+
*
37+
* @author ForteScarlet
38+
*/
39+
class ChannelMoveUserApiTest {
40+
41+
@Test
42+
fun testApiBasics() {
43+
val targetId = "voice_channel_123"
44+
val userIds = listOf("user_1", "user_2", "user_3")
45+
val api = ChannelMoveUserApi.create(targetId, userIds)
46+
47+
// Test API properties
48+
assertEquals(HttpMethod.Post, api.method)
49+
assertTrue(api.url.toString().contains("channel/move-user"))
50+
51+
// Test body content
52+
val body = api.body
53+
assertNotNull(body, "Body should not be null")
54+
}
55+
56+
@Test
57+
fun testUrlConstruction() {
58+
val api = ChannelMoveUserApi.create("test_channel", listOf("test_user"))
59+
val url = api.url.toString()
60+
61+
// Verify URL contains correct Kook API base and path
62+
assertEquals("https://www.kookapp.cn/api/v3/channel/move-user", url)
63+
}
64+
65+
@Test
66+
fun testRequestBodyNotNull() {
67+
val targetId = "voice_channel_456"
68+
val userIds = listOf("user_123", "user_456")
69+
val api = ChannelMoveUserApi.create(targetId, userIds)
70+
71+
val body = api.body
72+
assertNotNull(body, "Body should not be null")
73+
74+
// Verify that the body object exists and is not null
75+
assertTrue(body.toString().isNotEmpty())
76+
}
77+
78+
@Test
79+
fun testMultipleApiInstances() {
80+
val api1 = ChannelMoveUserApi.create("channel1", listOf("user1"))
81+
val api2 = ChannelMoveUserApi.create("channel2", listOf("user2"))
82+
83+
// Verify that different instances have different bodies
84+
val body1 = api1.body
85+
val body2 = api2.body
86+
87+
assertNotNull(body1)
88+
assertNotNull(body2)
89+
90+
// Bodies should be different objects (different parameters)
91+
assertTrue(body1 !== body2, "Different API instances should have different body objects")
92+
}
93+
94+
@Test
95+
fun testApiFactory() {
96+
val targetId = "factory_test_channel"
97+
val userIds = listOf("factory_test_user1", "factory_test_user2")
98+
99+
// Test factory method with List
100+
val api = ChannelMoveUserApi.create(targetId, userIds)
101+
assertNotNull(api)
102+
103+
// Verify the created API has the correct properties
104+
assertEquals(HttpMethod.Post, api.method)
105+
assertNotNull(api.body)
106+
assertNotNull(api.url)
107+
}
108+
109+
@Test
110+
fun testVarargsFactory() {
111+
val targetId = "varargs_test_channel"
112+
113+
// Test factory method with varargs
114+
val api = ChannelMoveUserApi.create(targetId, "user1", "user2", "user3")
115+
assertNotNull(api)
116+
117+
// Verify the created API has the correct properties
118+
assertEquals(HttpMethod.Post, api.method)
119+
assertNotNull(api.body)
120+
assertNotNull(api.url)
121+
assertTrue(api.url.toString().contains("channel/move-user"))
122+
}
123+
124+
@Test
125+
fun testApiPathCorrectness() {
126+
val api = ChannelMoveUserApi.create("test", listOf("test"))
127+
val url = api.url
128+
129+
// Check that the URL path is correct
130+
assertTrue(url.pathSegments.contains("channel"))
131+
assertTrue(url.pathSegments.contains("move-user"))
132+
133+
// Verify it's a POST API
134+
assertEquals(HttpMethod.Post, api.method)
135+
}
136+
137+
@Test
138+
fun testBodyNotNull() {
139+
val api = ChannelMoveUserApi.create("channel_test", listOf("user_test"))
140+
141+
// Body should never be null for this API
142+
assertNotNull(api.body)
143+
144+
// Multiple calls to body should return the same non-null value
145+
val body1 = api.body
146+
val body2 = api.body
147+
assertNotNull(body1)
148+
assertNotNull(body2)
149+
}
150+
151+
@Test
152+
fun testSingleUserList() {
153+
val targetId = "single_user_channel"
154+
val userId = "single_user"
155+
val api = ChannelMoveUserApi.create(targetId, listOf(userId))
156+
157+
// Verify API works with single user in list
158+
assertNotNull(api.body)
159+
assertEquals(HttpMethod.Post, api.method)
160+
assertTrue(api.url.toString().contains("channel/move-user"))
161+
}
162+
163+
@Test
164+
fun testMultipleUsers() {
165+
val targetId = "multi_user_channel"
166+
val userIds = listOf("user1", "user2", "user3", "user4", "user5")
167+
val api = ChannelMoveUserApi.create(targetId, userIds)
168+
169+
// Verify API works with multiple users
170+
assertNotNull(api.body)
171+
assertEquals(HttpMethod.Post, api.method)
172+
assertTrue(api.url.toString().contains("channel/move-user"))
173+
}
174+
175+
@Test
176+
fun testEmptyUserList() {
177+
val targetId = "empty_user_channel"
178+
val userIds = emptyList<String>()
179+
val api = ChannelMoveUserApi.create(targetId, userIds)
180+
181+
// Verify API works with empty user list (edge case)
182+
assertNotNull(api.body)
183+
assertEquals(HttpMethod.Post, api.method)
184+
assertTrue(api.url.toString().contains("channel/move-user"))
185+
}
186+
187+
@Test
188+
fun testApiRequestResult() = runTest {
189+
val targetId = "voice_channel_123"
190+
val userIds = listOf("user_1", "user_2")
191+
val api = ChannelMoveUserApi.create(targetId, userIds)
192+
val authorization = "Bot test_token"
193+
194+
// Mock response data - array of objects as per KOOK API documentation
195+
val responseData = """[
196+
{
197+
"id": "user_1",
198+
"username": "TestUser1",
199+
"identify_num": "1234",
200+
"online": true,
201+
"status": 1
202+
},
203+
{
204+
"id": "user_2",
205+
"username": "TestUser2",
206+
"identify_num": "5678",
207+
"online": true,
208+
"status": 1
209+
}
210+
]"""
211+
212+
// Wrap in KOOK API result format
213+
val apiResultJson = """{
214+
"code": 0,
215+
"message": "操作成功",
216+
"data": $responseData
217+
}"""
218+
219+
val mockEngine = MockEngine { _ ->
220+
respond(
221+
content = apiResultJson,
222+
status = HttpStatusCode.OK,
223+
headers = headersOf(HttpHeaders.ContentType, "application/json")
224+
)
225+
}
226+
227+
val client = HttpClient(mockEngine)
228+
val result = api.requestResult(client, authorization)
229+
230+
assertTrue(result.isSuccess)
231+
assertEquals(0, result.code)
232+
assertEquals("操作成功", result.message)
233+
assertNotNull(result.data)
234+
}
235+
236+
@Test
237+
fun testApiRequestData() = runTest {
238+
val targetId = "voice_channel_456"
239+
val userIds = listOf("user_3", "user_4", "user_5")
240+
val api = ChannelMoveUserApi.create(targetId, userIds)
241+
val authorization = "Bot test_token"
242+
243+
// Mock response data - array of JsonElement objects
244+
val responseData = """[
245+
{
246+
"id": "user_3",
247+
"username": "TestUser3",
248+
"identify_num": "9999",
249+
"online": true,
250+
"status": 1
251+
},
252+
{
253+
"id": "user_4",
254+
"username": "TestUser4",
255+
"identify_num": "8888",
256+
"online": true,
257+
"status": 1
258+
},
259+
{
260+
"id": "user_5",
261+
"username": "TestUser5",
262+
"identify_num": "7777",
263+
"online": false,
264+
"status": 0
265+
}
266+
]"""
267+
268+
// Wrap in KOOK API result format
269+
val apiResultJson = """{
270+
"code": 0,
271+
"message": "操作成功",
272+
"data": $responseData
273+
}"""
274+
275+
val mockEngine = MockEngine { _ ->
276+
respond(
277+
content = apiResultJson,
278+
status = HttpStatusCode.OK,
279+
headers = headersOf(HttpHeaders.ContentType, "application/json")
280+
)
281+
}
282+
283+
val client = HttpClient(mockEngine)
284+
val dataList = api.requestData(client, authorization)
285+
286+
assertNotNull(dataList)
287+
assertEquals(3, dataList.size)
288+
289+
// Verify we got JsonElement objects back
290+
assertTrue(dataList.all { it.toString().isNotEmpty() })
291+
}
292+
}

0 commit comments

Comments
 (0)