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 {