diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ccdc481c4e..72d504f1ec 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,6 +5,7 @@ on:
branches-ignore:
- "gh-pages"
- "root"
+ - "v2.2.1"
pull_request:
merge_group:
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 806b3d7979..e12f584246 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -3,11 +3,11 @@ name: "CodeQL"
on:
push:
branches:
- - root
+ - "root"
pull_request:
branches:
- - root
+ - "root"
schedule:
- cron: '19 21 * * 4'
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 4c00789228..500e6b83e8 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -4,6 +4,7 @@ on:
push:
branches:
- "root"
+ - "v2.2.1"
permissions:
contents: write
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 30da024912..bc829455de 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -6,7 +6,6 @@
-
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index e407c8e9c0..8c6e3ecbce 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,8 +3,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
repositories {
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
}
}
@@ -14,7 +14,6 @@ plugins {
kotlin("jvm")
- id("com.github.jakemarsden.git-hooks")
id("org.jetbrains.dokka")
}
@@ -36,15 +35,11 @@ repositories {
mavenCentral()
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
}
-gitHooks {
- setHooks(mapOf("pre-commit" to "applyLicenses detekt"))
-}
-
subprojects {
group = "dev.kordex"
version = projectVersion
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 1d71625112..ea8c72ea54 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -12,7 +12,6 @@ dependencies {
implementation(kotlin("serialization", version = "2.0.20"))
implementation("com.github.ben-manes", "gradle-versions-plugin", "0.51.0")
- implementation("com.github.jakemarsden", "git-hooks-gradle-plugin", "0.0.2")
implementation("com.google.devtools.ksp", "com.google.devtools.ksp.gradle.plugin", "2.0.20-1.0.24")
implementation("dev.yumi", "yumi-gradle-licenser", "1.2.0")
implementation("io.gitlab.arturbosch.detekt", "detekt-gradle-plugin", "1.23.6")
diff --git a/buildSrc/src/main/kotlin/kordex-module.gradle.kts b/buildSrc/src/main/kotlin/kordex-module.gradle.kts
index 7a9a18e704..eb45fa2d0e 100644
--- a/buildSrc/src/main/kotlin/kordex-module.gradle.kts
+++ b/buildSrc/src/main/kotlin/kordex-module.gradle.kts
@@ -55,8 +55,8 @@ repositories {
}
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
}
diff --git a/kord-extensions/build.gradle.kts b/kord-extensions/build.gradle.kts
index 7920dff695..4045d7c2d3 100644
--- a/kord-extensions/build.gradle.kts
+++ b/kord-extensions/build.gradle.kts
@@ -1,8 +1,8 @@
buildscript {
repositories {
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
}
}
diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/ExtensibleBot.kt
index 73105d2807..0b261a3c7c 100644
--- a/kord-extensions/src/main/kotlin/dev/kordex/core/ExtensibleBot.kt
+++ b/kord-extensions/src/main/kotlin/dev/kordex/core/ExtensibleBot.kt
@@ -53,6 +53,7 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement
import org.koin.core.component.inject
import org.koin.dsl.bind
+import kotlin.Throws
import kotlin.concurrent.thread
/**
@@ -74,6 +75,10 @@ public open class ExtensibleBot(
override var mutex: Mutex? = Mutex()
override var locking: Boolean = settings.membersBuilder.lockMemberRequests
+ @OptIn(DelicateCoroutinesApi::class)
+ public val interactionCoroutineContext: CoroutineDispatcher =
+ newFixedThreadPoolContext(settings.interactionContextThreads, "kord-extensions-interactions")
+
/** @suppress Meant for internal use by public inline function. **/
public val kordRef: Kord by inject()
@@ -140,14 +145,14 @@ public open class ExtensibleBot(
logger.debug { "Kord event filter predicate not set." }
kord.on {
- send(this@on)
+ sendKord(this@on)
}
} else {
logger.debug { "Kord event filter predicate set, filtering Kord events." }
kord.on {
if (settings.kordEventFilter!!(this@on)) {
- send(this@on)
+ sendKord(this@on)
}
}
}
@@ -185,6 +190,7 @@ public open class ExtensibleBot(
**/
public open suspend fun stop() {
dataCollector.stop()
+ interactionCoroutineContext.cancel()
getKoin().get().logout()
}
@@ -215,6 +221,8 @@ public open class ExtensibleBot(
}
dataCollector.stop()
+ interactionCoroutineContext.cancel()
+
getKoin().get().shutdown()
KordExContext.stopKoin()
@@ -227,32 +235,40 @@ public open class ExtensibleBot(
}
/** This function sets up all of the bot's default event listeners. **/
+ @Suppress("TooGenericExceptionCaught")
public open suspend fun registerListeners() {
val eventJson = Json {
ignoreUnknownKeys = true
}
on {
- dataCollector.start()
+ try {
+ dataCollector.start()
+ } catch (e: Exception) {
+ logger.warn(e) { "Exception thrown while setting up data collector" }
+ }
}
on {
withLock { // If configured, this won't be concurrent, saving larger bots from spammy rate limits
- if (
- settings.membersBuilder.guildsToFill == null ||
- settings.membersBuilder.guildsToFill!!.contains(guild.id)
- ) {
- logger.debug { "Requesting members for guild: ${guild.name}" }
-
- guild.requestMembers {
- presences = settings.membersBuilder.fillPresences
- requestAllMembers()
- }.collect()
+ try {
+ if (
+ settings.membersBuilder.guildsToFill == null ||
+ settings.membersBuilder.guildsToFill!!.contains(guild.id)
+ ) {
+ logger.debug { "Requesting members for guild: ${guild.name}" }
+
+ guild.requestMembers {
+ presences = settings.membersBuilder.fillPresences
+ requestAllMembers()
+ }.collect()
+ }
+ } catch (e: Exception) {
+ logger.error(e) { "Exception thrown while requesting guild members" }
}
}
}
- @Suppress("TooGenericExceptionCaught")
on {
try {
val eventObj = when (name) {
@@ -268,8 +284,8 @@ public open class ExtensibleBot(
GuildJoinRequestUpdateEvent(data)
}
- else -> null
- } ?: return@on
+ else -> return@on
+ }
send(eventObj)
} catch (e: Exception) {
@@ -281,21 +297,13 @@ public open class ExtensibleBot(
logger.warn { "Disconnected: $closeCode" }
}
- on {
- getKoin().get().handle(this)
- }
-
- on {
- getKoin().get().handle(this)
- }
-
- on {
- getKoin().get().handle(this)
- }
-
if (settings.chatCommandsBuilder.enabled) {
on {
- getKoin().get().handleEvent(this)
+ try {
+ getKoin().get().handleEvent(this)
+ } catch (e: Exception) {
+ logger.error(e) { "Exception thrown while handling messsage creation event for chat commands" }
+ }
}
} else {
logger.debug {
@@ -305,23 +313,11 @@ public open class ExtensibleBot(
}
if (settings.applicationCommandsBuilder.enabled) {
- on {
- getKoin().get().handle(this)
- }
-
- on {
- getKoin().get().handle(this)
- }
-
- on {
- getKoin().get().handle(this)
- }
-
- on {
- getKoin().get().handle(this)
+ try {
+ getKoin().get().initialRegistration()
+ } catch (e: Exception) {
+ logger.error(e) { "Exception thrown during initial interaction command registration phase" }
}
-
- getKoin().get().initialRegistration()
} else {
logger.debug {
"Application command support is disabled - set `enabled` to `true` in the " +
@@ -332,7 +328,7 @@ public open class ExtensibleBot(
if (!initialized) {
eventHandlers.forEach { handler ->
handler.listenerRegistrationCallable?.invoke() ?: logger.error {
- "Event handler $handler does not have a listener registration callback. This should never happen!"
+ "Event handler $handler doesn't have a listener registration callback. This should never happen!"
}
}
@@ -378,23 +374,117 @@ public open class ExtensibleBot(
try {
consumer(it)
} catch (t: Throwable) {
- logger.error(t) { "Error thrown from low-level event handler: $consumer" }
+ logger.error(t) { "Exception thrown from low-level event handler: $consumer" }
}
}
} else {
consumer(it)
}
- }.onFailure { logger.error(it) { "Error thrown from low-level event handler: $consumer" } }
- }.catch { logger.error(it) { "Error thrown from low-level event handler: $consumer" } }
+ }.onFailure { logger.error(it) { "Exception thrown from low-level event handler: $consumer" } }
+ }.catch { logger.error(it) { "Exception thrown from low-level event handler: $consumer" } }
.launchIn(kordRef)
/**
- * @suppress
+ * @suppress Internal function used to additionally process all events. Don't call this yourself.
*/
- public suspend inline fun send(event: Event) {
+ @Suppress("TooGenericExceptionCaught")
+ public suspend inline fun publishEvent(event: Event) {
+ when (event) {
+ // General interaction events
+ is ButtonInteractionCreateEvent ->
+ kordRef.launch(interactionCoroutineContext) {
+ try {
+ getKoin().get().handle(event)
+ } catch (e: Exception) {
+ logger.error(e) { "Exception thrown while handling button interaction event" }
+ }
+ }
+
+ is SelectMenuInteractionCreateEvent ->
+ kordRef.launch(interactionCoroutineContext) {
+ try {
+ getKoin().get().handle(event)
+ } catch (e: Exception) {
+ logger.error(e) { "Exception thrown while handling select menu interaction event" }
+ }
+ }
+
+ is ModalSubmitInteractionCreateEvent ->
+ kordRef.launch(interactionCoroutineContext) {
+ try {
+ getKoin().get().handle(event)
+ } catch (e: Exception) {
+ logger.error(e) { "Exception thrown while handling modal interaction event" }
+ }
+ }
+
+ // Interaction command events
+ is ChatInputCommandInteractionCreateEvent ->
+ if (settings.applicationCommandsBuilder.enabled) {
+ kordRef.launch(interactionCoroutineContext) {
+ try {
+ getKoin().get().handle(event)
+ } catch (e: Exception) {
+ logger.error(e) { "Exception thrown while handling slash command interaction event" }
+ }
+ }
+ }
+
+ is MessageCommandInteractionCreateEvent ->
+ if (settings.applicationCommandsBuilder.enabled) {
+ kordRef.launch(interactionCoroutineContext) {
+ try {
+ getKoin().get().handle(event)
+ } catch (e: Exception) {
+ logger.error(e) { "Exception thrown while handling message command interaction event" }
+ }
+ }
+ }
+
+ is UserCommandInteractionCreateEvent ->
+ if (settings.applicationCommandsBuilder.enabled) {
+ kordRef.launch(interactionCoroutineContext) {
+ try {
+ getKoin().get().handle(event)
+ } catch (e: Exception) {
+ logger.error(e) { "Exception thrown while handling user command interaction event" }
+ }
+ }
+ }
+
+ is AutoCompleteInteractionCreateEvent ->
+ if (settings.applicationCommandsBuilder.enabled) {
+ kordRef.launch(interactionCoroutineContext) {
+ try {
+ getKoin().get().handle(event)
+ } catch (e: Exception) {
+ logger.error(e) { "Exception thrown while handling autocomplete interaction event" }
+ }
+ }
+ }
+ }
+
eventPublisher.emit(event)
}
+ /**
+ * @suppress Internal function used to process Kord events. Don't call this yourself.
+ */
+ protected suspend inline fun sendKord(event: Event) {
+ publishEvent(event)
+ }
+
+ /**
+ * Submit an event, triggering any relevant event handlers.
+ *
+ * Events submitted using this function are filtered by [ExtensibleBotBuilder.kordEventFilter].
+ */
+ public suspend inline fun send(event: Event, filter: Boolean = true) {
+ if (!filter || settings.kordExEventFilter?.invoke(event) != false) {
+ publishEvent(event)
+ }
+ }
+
/**
* Install an [Extension] to this bot.
*
@@ -404,7 +494,6 @@ public open class ExtensibleBot(
* @param builder Builder function (or extension constructor) that takes an [ExtensibleBot] instance and
* returns an [Extension].
*/
- @Throws(InvalidExtensionException::class)
public open suspend fun addExtension(builder: () -> Extension) {
val extensionObj = builder.invoke()
@@ -439,7 +528,6 @@ public open class ExtensibleBot(
*
* @param extension The name of the [Extension] to unload.
*/
- @Throws(InvalidExtensionException::class)
public open suspend fun loadExtension(extension: String) {
val extensionObj = extensions[extension] ?: return
diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/builders/ExtensibleBotBuilder.kt
index 0810b64e3a..36c4cf2ad4 100644
--- a/kord-extensions/src/main/kotlin/dev/kordex/core/builders/ExtensibleBotBuilder.kt
+++ b/kord-extensions/src/main/kotlin/dev/kordex/core/builders/ExtensibleBotBuilder.kt
@@ -84,6 +84,13 @@ public open class ExtensibleBotBuilder {
/** Called to create an [ExtensibleBot], can be set to the constructor of your own subtype if needed. **/
public var constructor: (ExtensibleBotBuilder, String) -> ExtensibleBot = ::ExtensibleBot
+ /**
+ * The number of threads to use for interaction event coroutines.
+ *
+ * Defaults to double the available CPU cores, as returned by `Runtime.getRuntime().availableProcessors()`.
+ */
+ public var interactionContextThreads: Int = Runtime.getRuntime().availableProcessors() * 2
+
/** @suppress Builder that shouldn't be set directly by the user. **/
public val aboutBuilder: AboutBuilder = AboutBuilder()
@@ -145,6 +152,9 @@ public open class ExtensibleBotBuilder {
/** @suppress Builder that shouldn't be set directly by the user. **/
public var kordEventFilter: (suspend Event.() -> Boolean)? = null
+ /** @suppress Builder that shouldn't be set directly by the user. **/
+ public var kordExEventFilter: (suspend Event.() -> Boolean)? = null
+
/** @suppress Builder that shouldn't be set directly by the user. **/
public open val extensionsBuilder: ExtensionsBuilder = ExtensionsBuilder()
@@ -206,11 +216,41 @@ public open class ExtensibleBotBuilder {
/**
* Set an event-filtering predicate, which may selectively prevent Kord events from being processed by returning
* `false`.
+ *
+ * This only filters events created by Kord.
+ * For events submitted by Kord Extensions or loaded extensions, see [kordExEventFilter].
*/
+ @Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Disambiguation: Renamed to kordEventFilter.",
+ replaceWith = ReplaceWith("kordEventFilter"),
+ )
public fun eventFilter(predicate: suspend Event.() -> Boolean) {
kordEventFilter = predicate
}
+ /**
+ * Set an event-filtering predicate, which may selectively prevent Kord-created events from being processed by
+ * returning `false`.
+ *
+ * This only filters events created by Kord.
+ * For events submitted by Kord Extensions or loaded extensions, see [kordExEventFilter].
+ */
+ public fun kordEventFilter(predicate: suspend Event.() -> Boolean) {
+ kordEventFilter = predicate
+ }
+
+ /**
+ * Set an event-filtering predicate, which may selectively prevent KordEx-created events from being processed by
+ * returning `false`.
+ *
+ * This only filters events submitted by Kord Extensions or loaded extensions.
+ * For events created by Kord, see [kordEventFilter].
+ */
+ public fun kordExEventFilter(predicate: suspend Event.() -> Boolean) {
+ kordExEventFilter = predicate
+ }
+
/**
* DSL function used to configure information about the bot.
*
diff --git a/modules/data/data-mongodb/build.gradle.kts b/modules/data/data-mongodb/build.gradle.kts
index 907aaf2f5f..1025430fad 100644
--- a/modules/data/data-mongodb/build.gradle.kts
+++ b/modules/data/data-mongodb/build.gradle.kts
@@ -12,8 +12,8 @@ metadata {
repositories {
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
}
diff --git a/modules/functionality/func-mappings/build.gradle.kts b/modules/functionality/func-mappings/build.gradle.kts
index fe72e5ff77..69cee1a8a1 100644
--- a/modules/functionality/func-mappings/build.gradle.kts
+++ b/modules/functionality/func-mappings/build.gradle.kts
@@ -14,8 +14,8 @@ metadata {
repositories {
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
maven {
diff --git a/modules/functionality/func-phishing/build.gradle.kts b/modules/functionality/func-phishing/build.gradle.kts
index 9847971428..819fcab0e5 100644
--- a/modules/functionality/func-phishing/build.gradle.kts
+++ b/modules/functionality/func-phishing/build.gradle.kts
@@ -15,8 +15,8 @@ metadata {
repositories {
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
}
diff --git a/modules/functionality/func-tags/build.gradle.kts b/modules/functionality/func-tags/build.gradle.kts
index 80ccfff1dc..220511bb8f 100644
--- a/modules/functionality/func-tags/build.gradle.kts
+++ b/modules/functionality/func-tags/build.gradle.kts
@@ -15,8 +15,8 @@ metadata {
repositories {
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
}
diff --git a/modules/functionality/func-welcome/build.gradle.kts b/modules/functionality/func-welcome/build.gradle.kts
index 9ccad99388..f654736f73 100644
--- a/modules/functionality/func-welcome/build.gradle.kts
+++ b/modules/functionality/func-welcome/build.gradle.kts
@@ -15,8 +15,8 @@ metadata {
repositories {
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
}
diff --git a/modules/integrations/pluralkit/build.gradle.kts b/modules/integrations/pluralkit/build.gradle.kts
index 28b109c71d..585e4193a5 100644
--- a/modules/integrations/pluralkit/build.gradle.kts
+++ b/modules/integrations/pluralkit/build.gradle.kts
@@ -17,8 +17,8 @@ metadata {
repositories {
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
}
diff --git a/modules/web/web-core/web-backend/build.gradle.kts b/modules/web/web-core/web-backend/build.gradle.kts
index 0b4574c8e2..ded18049da 100644
--- a/modules/web/web-core/web-backend/build.gradle.kts
+++ b/modules/web/web-core/web-backend/build.gradle.kts
@@ -20,8 +20,8 @@ dokkaModule {
repositories {
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
}
diff --git a/test-bot/build.gradle.kts b/test-bot/build.gradle.kts
index d8c397b545..55ad22d708 100644
--- a/test-bot/build.gradle.kts
+++ b/test-bot/build.gradle.kts
@@ -3,8 +3,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
repositories {
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
}
}
@@ -18,8 +18,8 @@ plugins {
repositories {
maven {
- name = "Sonatype Snapshots"
- url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ name = "Kord Snapshots"
+ url = uri("https://repo.kord.dev/snapshots")
}
maven {