Skip to content

Commit 01cc419

Browse files
authored
Merge pull request #215 from simple-robot/dev/main
Release: v1.8.1
2 parents ca84aba + 09f2e40 commit 01cc419

File tree

7 files changed

+229
-25
lines changed

7 files changed

+229
-25
lines changed

buildSrc/src/main/kotlin/P.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ object P {
3737
override val description: String get() = DESCRIPTION
3838
override val homepage: String get() = HOMEPAGE
3939

40-
const val VERSION = "1.8.0"
41-
const val NEXT_VERSION = "1.8.1"
40+
const val VERSION = "1.8.1"
41+
const val NEXT_VERSION = "1.8.2"
4242

4343
override val snapshotVersion = "$NEXT_VERSION-SNAPSHOT"
4444
override val version = if (isSnapshot()) snapshotVersion else VERSION

settings.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pluginManagement {
2323
}
2424

2525
plugins {
26-
id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0"
26+
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
2727
}
2828

2929
rootProject.name = "simbot-component-onebot"

simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/OneBotBot.kt

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,85 @@ public interface OneBotBot : Bot, OneBotApiExecutable {
7272
override val coroutineContext: CoroutineContext
7373

7474
/**
75-
* [OneBotBot] 会使用的 [Json]
75+
* [OneBotBot] 会使用的 [Json]。
76+
* 需要在
7677
*/
7778
public val decoderJson: Json
7879

7980
/**
8081
* 当前Bot的配置类。
82+
*
83+
* 配置信息在 [initConfiguration] 或 [start] 调用后的修改无效。
8184
*/
8285
public val configuration: OneBotBotConfiguration
8386

87+
/**
88+
* 根据 [configuration] 初始化部分配置信息。
89+
*
90+
* 在一些特殊情况下(例如测试或仅需要一些序列器等),
91+
* 如果希望在不 [启动][start] bot 就初始化配置信息则使用此函数。
92+
*
93+
* 初始化配置只能执行一次。如果 [isConfigurationInitialized] 为 `true` 则会返回 `false`。
94+
* 同一时间只会有一个 [initConfiguration] 被执行。如果出现竞争则会挂起,
95+
* 直到其他竞争者完成初始化或出现异常。
96+
* 如果其他竞争者完成初始化,则会直接返回 `false`,不会重复初始化。
97+
*
98+
* [start] 中也会使用此函数。
99+
*
100+
* NOTE: 未来标准库 [Bot] 中添加了 `init` 相关函数和属性后会被废弃。
101+
* 参考 [#1071](https://github.com/simple-robot/simpler-robot/issues/1071)
102+
*
103+
* ### status
104+
*
105+
* 初始化状态有三个阶段:未初始化、正在初始化和完成初始化。
106+
* 根据三个状态的不同,会影响 [isConfigurationInitialized]
107+
* 和 [isConfigurationInitializing] 的值。
108+
*
109+
* | status | [isConfigurationInitializing] | [isConfigurationInitialized] |
110+
* | --- | --- | --- |
111+
* | 未初始化 | `false` | `false` |
112+
* | 初始化中 | `true` | `false` |
113+
* | 已初始化 | `false` | `true` |
114+
*
115+
* @throws RuntimeException 初始化过程中出现的任何非预期异常。
116+
* @return 如果已经初始化过了,则不会重复初始化,直接返回 `false`。
117+
* 否则在成功初始化后返回 `true`。
118+
*
119+
* @since 1.8.1
120+
*/
121+
@ExperimentalOneBotAPI
122+
public suspend fun initConfiguration(): Boolean
123+
124+
/**
125+
* 获取是否已经执行过 [initConfiguration]。
126+
* 如果 [initConfiguration] 尚在执行,会返回 `false`,
127+
* 如果 [initConfiguration] 已经成功,会得到 `true`。
128+
*
129+
* NOTE: 未来标准库 [Bot] 中添加了 `init` 相关函数和属性后会被废弃。
130+
* 参考 [#1071](https://github.com/simple-robot/simpler-robot/issues/1071)
131+
*
132+
* @see initConfiguration
133+
*
134+
* @since 1.8.1
135+
*/
136+
@ExperimentalOneBotAPI
137+
public val isConfigurationInitialized: Boolean
138+
139+
/**
140+
* 获取 [initConfiguration] 是否正在处于初始化过程中。
141+
* 如果 [initConfiguration] 已经成功或尚未开始,会得到 `false`,
142+
* 如果 [initConfiguration] 尚在执行,会返回 `true`。
143+
*
144+
* NOTE: 未来标准库 [Bot] 中添加了 `init` 相关函数和属性后会被废弃。
145+
* 参考 [#1071](https://github.com/simple-robot/simpler-robot/issues/1071)
146+
*
147+
* @see initConfiguration
148+
*
149+
* @since 1.8.1
150+
*/
151+
@ExperimentalOneBotAPI
152+
public val isConfigurationInitializing: Boolean
153+
84154
/**
85155
* 由 [OneBotBot] 衍生出的 actor 使用的 [CoroutineContext]。
86156
* 源自 [coroutineContext], 但是不包含 [Job][kotlinx.coroutines.Job]。

simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonMain/kotlin/love/forte/simbot/component/onebot/v11/core/bot/internal/OneBotBotImpl.kt

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import kotlinx.serialization.json.long
3636
import kotlinx.serialization.modules.overwriteWith
3737
import love.forte.simbot.annotations.FragileSimbotAPI
3838
import love.forte.simbot.bot.JobBasedBot
39+
import love.forte.simbot.common.atomic.atomic
3940
import love.forte.simbot.common.collectable.Collectable
4041
import love.forte.simbot.common.collectable.flowCollectable
4142
import love.forte.simbot.common.coroutines.IOOrDefault
@@ -86,6 +87,8 @@ import love.forte.simbot.logger.isDebugEnabled
8687
import kotlin.concurrent.Volatile
8788
import kotlin.coroutines.CoroutineContext
8889
import kotlin.math.max
90+
import kotlin.properties.Delegates
91+
import kotlin.time.Duration
8992
import kotlin.time.Duration.Companion.milliseconds
9093
import kotlin.time.Duration.Companion.seconds
9194
import love.forte.simbot.component.onebot.v11.event.RawEvent as OBRawEvent
@@ -102,46 +105,107 @@ internal class OneBotBotImpl(
102105
override val configuration: OneBotBotConfiguration,
103106
override val component: OneBot11Component,
104107
private val eventProcessor: EventProcessor,
105-
baseDecoderJson: Json,
108+
private val baseDecoderJson: Json,
106109
) : OneBotBot, JobBasedBot() {
107110
companion object {
108111
private const val BASE_LOGGER_NAME =
109112
"love.forte.simbot.component.onebot.v11.core.bot.OneBotBot"
110113
}
111114

112115
override val subContext = coroutineContext.minusKey(Job)
113-
override val decoderJson: Json = Json(baseDecoderJson) {
114-
configuration.serializersModule?.also { confMd ->
115-
serializersModule = serializersModule overwriteWith confMd
116-
}
117-
}
118116

119117
internal val logger = LoggerFactory.getLogger("$BASE_LOGGER_NAME.$uniqueId")
120118

121-
private val eventServerHost = configuration.eventServerHost
122-
private val connectMaxRetryTimes = configuration.wsConnectMaxRetryTimes
123-
private val connectRetryDelay = max(configuration.wsConnectRetryDelayMillis, 0L).milliseconds
119+
override lateinit var decoderJson: Json
120+
private set
121+
122+
private var eventServerHost: Url? = null
123+
private var connectMaxRetryTimes by Delegates.notNull<Int>()
124+
private var connectRetryDelay by Delegates.notNull<Duration>()
125+
126+
override lateinit var apiHost: Url
127+
override var apiAccessToken: String? = null
128+
override var eventAccessToken: String? = null
124129

125-
override val apiClient: HttpClient
126-
private val wsClient: HttpClient?
130+
131+
override lateinit var apiClient: HttpClient
132+
private set
133+
134+
private var wsClient: HttpClient? = null
127135

128136
@ExperimentalCustomEventResolverApi
129-
internal val customEventResolvers = configuration.customEventResolvers.toList()
137+
internal lateinit var customEventResolvers: List<CustomEventResolver>
130138

131-
init {
132-
apiClient = resolveHttpClient()
133-
wsClient = if (eventServerHost != null) {
139+
private val initLock = Mutex()
140+
private val initialized = atomic(false)
141+
142+
override suspend fun initConfiguration(): Boolean {
143+
if (initialized.value) {
144+
return false
145+
}
146+
147+
initLock.withLock {
148+
if (initialized.value) {
149+
return false
150+
}
151+
152+
initDecoderJson()
153+
initHostAndConnectAndToken()
154+
initClients()
155+
initJobCompletion()
156+
initCustomEventResolvers()
157+
158+
initialized.value = true
159+
}
160+
161+
return true
162+
}
163+
164+
override val isConfigurationInitialized: Boolean
165+
get() = initialized.value
166+
167+
override val isConfigurationInitializing: Boolean
168+
get() = !isConfigurationInitialized && initLock.isLocked
169+
170+
private fun initDecoderJson() {
171+
decoderJson = Json(baseDecoderJson) {
172+
configuration.serializersModule?.also { confMd ->
173+
serializersModule = serializersModule overwriteWith confMd
174+
}
175+
}
176+
}
177+
178+
private fun initHostAndConnectAndToken() {
179+
this.eventServerHost = configuration.eventServerHost
180+
this.connectMaxRetryTimes = configuration.wsConnectMaxRetryTimes
181+
this.connectRetryDelay = max(configuration.wsConnectRetryDelayMillis, 0L).milliseconds
182+
this.apiHost = configuration.apiServerHost
183+
this.apiAccessToken = configuration.apiAccessToken
184+
this.eventAccessToken = configuration.eventAccessToken
185+
186+
}
187+
188+
private fun initClients() {
189+
this.apiClient = resolveHttpClient()
190+
this.wsClient = if (eventServerHost != null) {
134191
resolveWsClient()
135192
} else {
136193
null
137194
}
195+
}
138196

197+
private fun initJobCompletion() {
139198
job.invokeOnCompletion {
140199
apiClient.close()
141200
wsClient?.close()
142201
}
143202
}
144203

204+
@OptIn(ExperimentalCustomEventResolverApi::class)
205+
private fun initCustomEventResolvers() {
206+
this.customEventResolvers = configuration.customEventResolvers.toList()
207+
}
208+
145209
private val wsEnabled: Boolean
146210
get() = wsClient != null
147211

@@ -214,10 +278,6 @@ internal class OneBotBotImpl(
214278
}
215279
}
216280

217-
override val apiHost: Url = configuration.apiServerHost
218-
219-
override val apiAccessToken: String? = configuration.apiAccessToken
220-
override val eventAccessToken: String? = configuration.eventAccessToken
221281

222282
override val id: ID = uniqueId.ID
223283

@@ -260,6 +320,7 @@ internal class OneBotBotImpl(
260320

261321
override suspend fun start(): Unit = startLock.withLock {
262322
job.ensureActive()
323+
initConfiguration()
263324

264325
// 更新个人信息
265326
val info = queryLoginInfo()
@@ -580,7 +641,7 @@ internal class OneBotBotImpl(
580641
override suspend fun member(groupId: ID, memberId: ID): OneBotMember {
581642
// TODO 如何检测不存在?
582643
return this@OneBotBotImpl.executeData(
583-
GetGroupMemberInfoApi.create(groupId, userId)
644+
GetGroupMemberInfoApi.create(groupId, memberId)
584645
).toMember(this@OneBotBotImpl)
585646
}
586647
}

simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonTest/kotlin/love/forte/simbot/component/onebot/v11/core/bot/BotDecoderFromCustomComponentTests.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class BotDecoderFromCustomComponentTests {
6060
assertEquals("test", testSeg.data!!.jsonPrimitive.content)
6161
}
6262

63-
private fun doTest(app: Application): OneBotMessageSegment {
63+
private suspend fun doTest(app: Application): OneBotMessageSegment {
6464
app.oneBot11Bots {
6565
val bot = register(
6666
OneBotBotConfiguration().apply {
@@ -73,6 +73,7 @@ class BotDecoderFromCustomComponentTests {
7373
}
7474
}
7575
)
76+
bot.initConfiguration()
7677

7778
val segments = doSerial(bot)
7879
assertEquals(3, segments.size)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package love.forte.simbot.component.onebot.v11.core.bot
2+
3+
import kotlinx.coroutines.SupervisorJob
4+
import kotlinx.coroutines.flow.Flow
5+
import kotlinx.coroutines.test.runTest
6+
import love.forte.simbot.component.onebot.v11.core.OneBot11
7+
import love.forte.simbot.component.onebot.v11.core.bot.internal.OneBotBotImpl
8+
import love.forte.simbot.component.onebot.v11.core.component.OneBot11Component
9+
import love.forte.simbot.event.Event
10+
import love.forte.simbot.event.EventProcessor
11+
import love.forte.simbot.event.EventResult
12+
import kotlin.coroutines.EmptyCoroutineContext
13+
import kotlin.test.Test
14+
import kotlin.test.assertFalse
15+
import kotlin.test.assertTrue
16+
17+
/**
18+
*
19+
* @author ForteScarlet
20+
*/
21+
class BotInitializationTests {
22+
private fun bot() = OneBotBotImpl(
23+
uniqueId = "UNIQUE_ID",
24+
coroutineContext = EmptyCoroutineContext,
25+
job = SupervisorJob(),
26+
configuration = OneBotBotConfiguration(),
27+
component = OneBot11Component(),
28+
eventProcessor = object : EventProcessor {
29+
override fun push(event: Event): Flow<EventResult> {
30+
TODO("Not yet implemented")
31+
}
32+
},
33+
baseDecoderJson = OneBot11.DefaultJson
34+
)
35+
36+
@Test
37+
fun testBotInit() = runTest {
38+
val bot = bot()
39+
40+
assertFalse(bot.isConfigurationInitialized)
41+
assertFalse(bot.isConfigurationInitializing)
42+
43+
assertTrue(bot.initConfiguration())
44+
assertTrue(bot.isConfigurationInitialized)
45+
assertFalse(bot.initConfiguration())
46+
47+
assertTrue(bot.isConfigurationInitialized)
48+
bot.cancel()
49+
}
50+
51+
@Test
52+
fun testBotStartInit() = runTest {
53+
val bot = bot()
54+
55+
assertFalse(bot.isConfigurationInitialized)
56+
assertFalse(bot.isConfigurationInitializing)
57+
58+
runCatching { bot.start() }
59+
60+
assertTrue(bot.isConfigurationInitialized)
61+
assertFalse(bot.initConfiguration())
62+
63+
assertTrue(bot.isConfigurationInitialized)
64+
65+
bot.cancel()
66+
}
67+
68+
69+
70+
}

simbot-component-onebot-v11/simbot-component-onebot-v11-core/src/commonTest/kotlin/love/forte/simbot/component/onebot/v11/core/event/CustomEventResolverTests.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ class CustomEventResolverTests {
7373
return register {
7474
botUniqueId = UUID.random().toString()
7575
config()
76+
}.also {
77+
it.initConfiguration()
7678
}
7779
}
7880
}

0 commit comments

Comments
 (0)