diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000..09a36aaff5
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+root = true
+
+[*]
+charset = utf-8
+insert_final_newline = true
+indent_style = tab
+trim_trailing_whitespace = true
+max_line_length = 120
+
+[*.yml]
+indent_style = space
+indent_size = 2
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..e1d211cbd7
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,15 @@
+version: 2
+enable-beta-ecosystems: true
+
+updates:
+ - package-ecosystem: "gradle"
+ directory: "/"
+
+ schedule:
+ interval: "weekly"
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index abeddda664..3d78e71f4d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,19 +7,21 @@ on:
- "root"
pull_request:
+ merge_group:
jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v4
- name: Set up Java
- uses: actions/setup-java@v1
+ uses: actions/setup-java@v3
with:
- java-version: 1.14
+ java-version: 17
+ distribution: temurin
- name: Set up Gradle properties
run: |
@@ -32,21 +34,21 @@ jobs:
arguments: checkLicenses build
- name: Upload artifact (Extra Module JARs)
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: JARs (Extra Modules)
path: extra-modules/*/build/libs/*.jar
- name: Upload artifact (Main JARs)
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: JARs (Main)
path: kord-extensions/build/libs/*.jar
- name: Upload artifact (Module JARs)
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: JARs (Modules)
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index b68c397613..6039b01371 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -5,18 +5,22 @@ on:
branches:
- "root"
+permissions:
+ contents: write
+
jobs:
publish:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v4
- name: Set up Java
- uses: actions/setup-java@v1
+ uses: actions/setup-java@v3
with:
- java-version: 1.14
+ java-version: 17
+ distribution: temurin
- name: Set up Kotlin
uses: fwilhe2/setup-kotlin@main
@@ -42,6 +46,7 @@ jobs:
with:
arguments: checkLicenses build
+ dependency-graph: generate-and-submit
- name: Disable parallel publish
if: contains(steps.project-version.outputs.version, '-SNAPSHOT')
@@ -64,21 +69,21 @@ jobs:
arguments: publish --no-parallel
- name: Upload artifact (Extra Module JARs)
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: JARs (Extra Modules)
path: extra-modules/*/build/libs/*.jar
- name: Upload artifact (Main JARs)
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: JARs (Main)
path: kord-extensions/build/libs/*.jar
- name: Upload artifact (Module JARs)
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: JARs (Modules)
diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml
index 4093c6ea45..9297b421be 100644
--- a/.github/workflows/tag.yml
+++ b/.github/workflows/tag.yml
@@ -10,13 +10,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v4
- name: Set up Java
- uses: actions/setup-java@v1
+ uses: actions/setup-java@v3
with:
- java-version: 1.14
+ java-version: 17
+ distribution: temurin
- name: Set up Kotlin
uses: fwilhe2/setup-kotlin@main
@@ -29,7 +30,7 @@ jobs:
echo -e "\nsigning.gnupg.passphrase=${{ secrets.GPG_PASSWORD }}" >> ~/.gradle/gradle.properties
- name: Set up git credentials
- uses: oleksiyrudenko/gha-git-credentials@v2-latest
+ uses: oleksiyrudenko/gha-git-credentials@v2
with:
global: true
@@ -82,21 +83,21 @@ jobs:
WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
- name: Upload artifact (Extra Module JARs)
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: JARs (Extra Modules)
path: extra-modules/*/build/libs/*.jar
- name: Upload artifact (Main JARs)
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: JARs (Main)
path: kord-extensions/build/libs/*.jar
- name: Upload artifact (Module JARs)
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: JARs (Modules)
diff --git a/.idea/kotlinScripting.xml b/.idea/kotlinScripting.xml
deleted file mode 100644
index 0224fb72c9..0000000000
--- a/.idea/kotlinScripting.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index af5b6f67e4..1ee496d87b 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 4ce150ac36..f7c708e3bb 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -2,6 +2,10 @@
+
+
+
+
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
index 797acea53e..a1fe99eb3b 100644
--- a/.idea/runConfigurations.xml
+++ b/.idea/runConfigurations.xml
@@ -7,4 +7,4 @@
-
\ No newline at end of file
+
diff --git a/annotation-processor/build.gradle.kts b/annotation-processor/build.gradle.kts
index dde4939d43..43825c3cf1 100644
--- a/annotation-processor/build.gradle.kts
+++ b/annotation-processor/build.gradle.kts
@@ -13,7 +13,6 @@ dependencies {
implementation(libs.kotlin.stdlib)
implementation(libs.koin.core)
- implementation(libs.kotlinpoet)
implementation(libs.ksp)
implementation(project(":annotations"))
diff --git a/annotation-processor/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/annotations/converters/builders/ConverterBuilderClassBuilder.kt b/annotation-processor/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/annotations/converters/builders/ConverterBuilderClassBuilder.kt
index 24c78a46ea..31a0b3fcf1 100644
--- a/annotation-processor/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/annotations/converters/builders/ConverterBuilderClassBuilder.kt
+++ b/annotation-processor/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/annotations/converters/builders/ConverterBuilderClassBuilder.kt
@@ -335,7 +335,8 @@ public class ConverterBuilderClassBuilder : KoinComponent {
ConverterType.COALESCING -> before.add("coalescing")
ConverterType.CHOICE -> after.add("choice")
- ConverterType.SINGLE -> { /* Don't add anything */
+ ConverterType.SINGLE -> {
+ /* Don't add anything */
}
}
}
@@ -378,23 +379,3 @@ public fun builderClass(body: ConverterBuilderClassBuilder.() -> Unit): Converte
return builder
}
-
-/** @suppress TODO: Remove this later, it's for testing **/
-public fun main() {
- println(
- builderClass {
- name = "enum"
- converterClass = "EnumConverter"
- argumentType = "E"
-
- builderGeneric = "E: Enum"
-
- builderArg("public var getter: suspend (String) -> E?")
-
- builderField("public lateinit var typeName: String")
- builderField("public var bundle: String? = null")
-
- types(ConverterType.COALESCING)
- }.result
- )
-}
diff --git a/annotation-processor/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/annotations/converters/builders/ConverterBuilderFunctionBuilder.kt b/annotation-processor/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/annotations/converters/builders/ConverterBuilderFunctionBuilder.kt
index 899b2dc90e..3514f19755 100644
--- a/annotation-processor/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/annotations/converters/builders/ConverterBuilderFunctionBuilder.kt
+++ b/annotation-processor/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/annotations/converters/builders/ConverterBuilderFunctionBuilder.kt
@@ -138,21 +138,3 @@ public fun builderFunction(body: ConverterBuilderFunctionBuilder.() -> Unit): St
return builder.build()
}
-
-/** @suppress TODO: Remove this later, it's for testing **/
-public fun main() {
- println(
- builderFunction {
- functionGeneric = "E: Enum"
- builderGeneric = "E: Enum"
-
- argumentType = "E"
- converterType = "SingleConverter"
-
- name = "enum"
- builderType = "EnumConverterBuilder"
-
- builderArg("getter = { getEnum(it) }")
- }
- )
-}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index e39c3aa2a0..b9be73494f 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -8,14 +8,15 @@ repositories {
}
dependencies {
- implementation(kotlin("gradle-plugin", version = "1.8.20"))
- implementation(kotlin("serialization", version = "1.8.20"))
+ implementation(kotlin("gradle-plugin", version = "1.9.10"))
+ implementation(kotlin("serialization", version = "1.9.10"))
implementation("gradle.plugin.org.cadixdev.gradle", "licenser", "0.6.1")
+ implementation("com.github.ben-manes", "gradle-versions-plugin", "0.47.0")
implementation("com.github.jakemarsden", "git-hooks-gradle-plugin", "0.0.2")
- implementation("com.google.devtools.ksp", "com.google.devtools.ksp.gradle.plugin", "1.8.20-1.0.11")
- implementation("io.gitlab.arturbosch.detekt", "detekt-gradle-plugin", "1.22.0")
- implementation("org.jetbrains.dokka", "dokka-gradle-plugin", "1.8.10")
+ implementation("com.google.devtools.ksp", "com.google.devtools.ksp.gradle.plugin", "1.9.10-1.0.13")
+ implementation("io.gitlab.arturbosch.detekt", "detekt-gradle-plugin", "1.23.1")
+ implementation("org.jetbrains.dokka", "dokka-gradle-plugin", "1.9.0")
implementation(gradleApi())
implementation(localGroovy())
diff --git a/buildSrc/src/main/kotlin/kordex-module.gradle.kts b/buildSrc/src/main/kotlin/kordex-module.gradle.kts
index 23d062226e..2b053f486c 100644
--- a/buildSrc/src/main/kotlin/kordex-module.gradle.kts
+++ b/buildSrc/src/main/kotlin/kordex-module.gradle.kts
@@ -4,6 +4,7 @@ plugins {
kotlin("jvm")
kotlin("plugin.serialization")
+ id("com.github.ben-manes.versions")
id("io.gitlab.arturbosch.detekt")
id("org.cadixdev.licenser")
}
@@ -60,7 +61,7 @@ tasks {
detekt {
buildUponDefaultConfig = true
- config = files("$rootDir/detekt.yml")
+ config.from(files("$rootDir/detekt.yml"))
autoCorrect = true
}
diff --git a/buildSrc/src/main/kotlin/ksp-module.gradle.kts b/buildSrc/src/main/kotlin/ksp-module.gradle.kts
index d6520fda39..c4fd513998 100644
--- a/buildSrc/src/main/kotlin/ksp-module.gradle.kts
+++ b/buildSrc/src/main/kotlin/ksp-module.gradle.kts
@@ -18,17 +18,17 @@ idea { // We use this instead of sourceSets b/c we're all IJ users and this fixe
module {
// Not using += due to https://github.com/gradle/gradle/issues/8749
sourceDirs = sourceDirs +
- file("$buildDir/generated/ksp/main/kotlin")
+ file("${layout.buildDirectory}/generated/ksp/main/kotlin")
testSources.setFrom(
- testSources.from + file("$buildDir/generated/ksp/test/kotlin")
+ testSources.from + file("${layout.buildDirectory}/generated/ksp/test/kotlin")
)
// testSourceDirs = testSourceDirs +
// file("$buildDir/generated/ksp/test/kotlin")
generatedSourceDirs = generatedSourceDirs +
- file("$buildDir/generated/ksp/main/kotlin") +
- file("$buildDir/generated/ksp/test/kotlin")
+ file("${layout.buildDirectory}/generated/ksp/main/kotlin") +
+ file("${layout.buildDirectory}/generated/ksp/test/kotlin")
}
}
diff --git a/data-adapters/adapter-mongodb/README.md b/data-adapters/adapter-mongodb/README.md
new file mode 100644
index 0000000000..e5aee2b4e3
--- /dev/null
+++ b/data-adapters/adapter-mongodb/README.md
@@ -0,0 +1,54 @@
+# MongoDB Adapter
+
+This module provides a data adapter (and some utilities) for MongoDB.
+This includes some codecs, for commonly-used types - which you can use in your own codec registry if you wish.
+
+# Usage
+
+* **Maven repo:** Maven Central for releases, `https://s01.oss.sonatype.org/content/repositories/snapshots` for
+ snapshots
+* **Maven coordinates:** `com.kotlindiscord.kord.extensions:adapter-mongodb:VERSION`
+
+To switch to the MongoDB data adapter follow these steps:
+
+1. Set the `ADAPTER_MONGODB_URI` environmental variable to a MongoDB connection string.
+2. Use the `mongoDB` function to set up the data adapter.
+
+ ```kotlin
+ suspend fun main() {
+ val bot = ExtensibleBot(System.getenv("TOKEN")) {
+ mongoDB()
+ }
+
+ bot.start()
+ }
+ ```
+
+3. If you use MongoDB elsewhere in your project, you can use the provided codecs to handle these types:
+ - `DateTimePeriod` (kotlinx Datetime)
+ - `Instant` (Kotlinx Datetime)
+ - `Snowflake` (Kord)
+ - `StorageType` (KordEx)
+
+ ```kotlin
+ // import: com.kotlindiscord.kord.extensions.adapters.mongodb.kordExCodecRegistry
+ val registry = CodecRegistries.fromRegistries(
+ kordExCodecRegistry,
+ MongoClientSettings.getDefaultCodecRegistry(),
+ )
+
+ val client = MongoClient.create(MONGODB_URI)
+ val database = client.getDatabase("database-name")
+
+ val collection = database.getCollection("name")
+ .withCodecRegistry(registry)
+ ```
+
+ For more information on working with codecs,
+ see [the MongoDB documentation](https://www.mongodb.com/docs/drivers/kotlin/coroutine/current/fundamentals/data-formats/codecs).
+
+# Notes
+
+* All provided codecs store their respective data types as strings in the database.
+* If you need to migrate from another data adapter to this one, you should read the code for both data adapters before
+ writing your own migration code.
diff --git a/data-adapters/adapter-mongodb/build.gradle.kts b/data-adapters/adapter-mongodb/build.gradle.kts
new file mode 100644
index 0000000000..13452b225d
--- /dev/null
+++ b/data-adapters/adapter-mongodb/build.gradle.kts
@@ -0,0 +1,31 @@
+plugins {
+ `kordex-module`
+ `published-module`
+ `dokka-module`
+}
+
+metadata {
+ name = "KordEx Adapters: MongoDB"
+ description = "KordEx data adapter for MongoDB, including extra codecs"
+}
+
+repositories {
+ maven {
+ name = "Sonatype Snapshots"
+ url = uri("https://oss.sonatype.org/content/repositories/snapshots")
+ }
+}
+
+dependencies {
+ detektPlugins(libs.detekt)
+ detektPlugins(libs.detekt.libraries)
+
+ implementation(libs.kotlin.stdlib)
+ implementation(libs.kx.coro)
+ implementation(libs.bundles.logging)
+ implementation(libs.mongodb)
+
+ implementation(project(":kord-extensions"))
+}
+
+group = "com.kotlindiscord.kord.extensions"
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/Constants.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/Constants.kt
new file mode 100644
index 0000000000..1f08a90196
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/Constants.kt
@@ -0,0 +1,11 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.adapters.mongodb
+
+import com.kotlindiscord.kord.extensions.utils.env
+
+internal val MONGODB_URI: String = env("ADAPTER_MONGODB_URI")
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/Functions.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/Functions.kt
new file mode 100644
index 0000000000..b2a795d81a
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/Functions.kt
@@ -0,0 +1,37 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.adapters.mongodb
+
+import com.kotlindiscord.kord.extensions.adapters.mongodb.db.Database
+import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder
+
+/**
+ * Configures the bot to use MongoDB as the data storage adapter.
+ *
+ * This method sets up the [MongoDBDataAdapter] as the data adapter for the bot, allowing it to interact with a
+ * MongoDB database for storing and retrieving data provided by storage units.
+ *
+ * Additionally, this method registers a hook with `beforeKoinSetup`, which checks that the database is reachable,
+ * and runs any pending migrations.
+ *
+ * Usage:
+ *
+ * ```
+ * ExtensibleBotBuilder(...) {
+ * mongoDB()
+ * }
+ * ```
+ */
+public suspend fun ExtensibleBotBuilder.mongoDB() {
+ dataAdapter(::MongoDBDataAdapter)
+
+ hooks {
+ beforeKoinSetup {
+ Database.setup()
+ }
+ }
+}
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/MongoDBDataAdapter.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/MongoDBDataAdapter.kt
new file mode 100644
index 0000000000..b542796e89
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/MongoDBDataAdapter.kt
@@ -0,0 +1,162 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+@file:Suppress("UNCHECKED_CAST")
+@file:OptIn(InternalSerializationApi::class)
+
+package com.kotlindiscord.kord.extensions.adapters.mongodb
+
+import com.kotlindiscord.kord.extensions.adapters.mongodb.db.AdaptedData
+import com.kotlindiscord.kord.extensions.adapters.mongodb.db.Database
+import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
+import com.kotlindiscord.kord.extensions.storage.Data
+import com.kotlindiscord.kord.extensions.storage.DataAdapter
+import com.kotlindiscord.kord.extensions.storage.StorageUnit
+import com.mongodb.client.model.Filters
+import com.mongodb.client.model.Filters.eq
+import com.mongodb.client.model.ReplaceOptions
+import com.mongodb.kotlin.client.coroutine.MongoCollection
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.serialization.InternalSerializationApi
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.serializer
+import org.bson.conversions.Bson
+
+/**
+ * This class represents a MongoDB data adapter for storing and retrieving data using MongoDB as the underlying
+ * database for data stored using storage units.
+ *
+ * Use the provided [mongoDB] function to add this to your bot, rather than directly referencing the constructor for
+ * this class.
+ */
+public class MongoDBDataAdapter : DataAdapter(), KordExKoinComponent {
+ private val collectionCache: MutableMap> = mutableMapOf()
+
+ private fun StorageUnit<*>.getIdentifier(): String =
+ buildString {
+ append("${storageType.type}/")
+
+ if (guild != null) append("guild-$guild/")
+ if (channel != null) append("channel-$channel/")
+ if (user != null) append("user-$user/")
+ if (message != null) append("message-$message/")
+
+ append(identifier)
+ }
+
+ private fun getCollection(namespace: String): MongoCollection {
+ val collName = "data-$namespace"
+
+ return collectionCache.getOrPut(collName) { Database.getCollection(collName) }
+ }
+
+ private fun constructQuery(unit: StorageUnit<*>): Bson =
+ Filters.and(
+ listOf(
+ eq(AdaptedData::identifier.name, unit.identifier),
+
+ eq(AdaptedData::type.name, unit.storageType),
+
+ eq(AdaptedData::channel.name, unit.channel),
+ eq(AdaptedData::guild.name, unit.guild),
+ eq(AdaptedData::message.name, unit.message),
+ eq(AdaptedData::user.name, unit.user)
+ )
+ )
+
+ override suspend fun delete(unit: StorageUnit): Boolean {
+ removeFromCache(unit)
+
+ val result = getCollection(unit.namespace)
+ .deleteOne(constructQuery(unit))
+
+ return result.deletedCount > 0
+ }
+
+ override suspend fun get(unit: StorageUnit): R? {
+ val dataId = unitCache[unit]
+
+ if (dataId != null) {
+ val data = dataCache[dataId]
+
+ if (data != null) {
+ return data as R
+ }
+ }
+
+ return reload(unit)
+ }
+
+ override suspend fun reload(unit: StorageUnit): R? {
+ val dataId = unit.getIdentifier()
+ val result = getCollection(unit.namespace)
+ .find(constructQuery(unit)).limit(1).firstOrNull()?.data
+
+ if (result != null) {
+ dataCache[dataId] = Json.decodeFromString(unit.dataType.serializer(), result)
+ unitCache[unit] = dataId
+ }
+
+ return dataCache[dataId] as R?
+ }
+
+ override suspend fun save(unit: StorageUnit): R? {
+ val data = get(unit) ?: return null
+
+ getCollection(unit.namespace).replaceOne(
+ eq(unit.getIdentifier()),
+
+ AdaptedData(
+ _id = unit.getIdentifier(),
+
+ identifier = unit.identifier,
+
+ type = unit.storageType,
+
+ channel = unit.channel,
+ guild = unit.guild,
+ message = unit.message,
+ user = unit.user,
+
+ data = Json.encodeToString(unit.dataType.serializer(), data)
+ ),
+
+ ReplaceOptions().upsert(true)
+ )
+
+ return data
+ }
+
+ override suspend fun save(unit: StorageUnit, data: R): R {
+ val dataId = unit.getIdentifier()
+
+ dataCache[dataId] = data
+ unitCache[unit] = dataId
+
+ getCollection(unit.namespace).replaceOne(
+ eq(unit.getIdentifier()),
+
+ AdaptedData(
+ _id = unit.getIdentifier(),
+
+ identifier = unit.identifier,
+
+ type = unit.storageType,
+
+ channel = unit.channel,
+ guild = unit.guild,
+ message = unit.message,
+ user = unit.user,
+
+ data = Json.encodeToString(unit.dataType.serializer(), data)
+ ),
+
+ ReplaceOptions().upsert(true)
+ )
+
+ return data
+ }
+}
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/Registry.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/Registry.kt
new file mode 100644
index 0000000000..c1ad551de1
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/Registry.kt
@@ -0,0 +1,16 @@
+package com.kotlindiscord.kord.extensions.adapters.mongodb
+
+import com.kotlindiscord.kord.extensions.adapters.mongodb.codecs.*
+import org.bson.codecs.configuration.CodecRegistries
+import org.bson.codecs.configuration.CodecRegistry
+
+public val kordExCodecRegistry: CodecRegistry = CodecRegistries.fromCodecs(
+ DateTimePeriodCodec,
+ InstantCodec,
+ SnowflakeCodec,
+ StorageTypeCodec,
+
+ // Required b/c the BSON codec library doesn't support standard polymorphism.
+ ConfigStorageTypeCodec,
+ DataStorageTypeCodec,
+)
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/DateTimePeriodCodec.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/DateTimePeriodCodec.kt
new file mode 100644
index 0000000000..64a39d846f
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/DateTimePeriodCodec.kt
@@ -0,0 +1,26 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.adapters.mongodb.codecs
+
+import kotlinx.datetime.DateTimePeriod
+import org.bson.BsonReader
+import org.bson.BsonWriter
+import org.bson.codecs.Codec
+import org.bson.codecs.DecoderContext
+import org.bson.codecs.EncoderContext
+
+public object DateTimePeriodCodec : Codec {
+ override fun decode(reader: BsonReader, decoderContext: DecoderContext): DateTimePeriod =
+ DateTimePeriod.parse(reader.readString())
+
+ override fun encode(writer: BsonWriter, value: DateTimePeriod, encoderContext: EncoderContext) {
+ writer.writeString(value.toString())
+ }
+
+ override fun getEncoderClass(): Class =
+ DateTimePeriod::class.java
+}
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/InstantCodec.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/InstantCodec.kt
new file mode 100644
index 0000000000..2b080d6655
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/InstantCodec.kt
@@ -0,0 +1,26 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.adapters.mongodb.codecs
+
+import kotlinx.datetime.Instant
+import org.bson.BsonReader
+import org.bson.BsonWriter
+import org.bson.codecs.Codec
+import org.bson.codecs.DecoderContext
+import org.bson.codecs.EncoderContext
+
+public object InstantCodec : Codec {
+ override fun decode(reader: BsonReader, decoderContext: DecoderContext): Instant =
+ Instant.parse(reader.readString())
+
+ override fun encode(writer: BsonWriter, value: Instant, encoderContext: EncoderContext) {
+ writer.writeString(value.toString())
+ }
+
+ override fun getEncoderClass(): Class =
+ Instant::class.java
+}
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/SnowflakeCodec.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/SnowflakeCodec.kt
new file mode 100644
index 0000000000..7b0c82a824
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/SnowflakeCodec.kt
@@ -0,0 +1,30 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.adapters.mongodb.codecs
+
+import dev.kord.common.entity.Snowflake
+import org.bson.BsonInvalidOperationException
+import org.bson.BsonReader
+import org.bson.BsonWriter
+import org.bson.codecs.Codec
+import org.bson.codecs.DecoderContext
+import org.bson.codecs.EncoderContext
+
+public object SnowflakeCodec : Codec {
+ override fun decode(reader: BsonReader, decoderContext: DecoderContext): Snowflake = try {
+ Snowflake(reader.readString())
+ } catch (e: BsonInvalidOperationException) {
+ Snowflake(reader.readInt64())
+ }
+
+ override fun encode(writer: BsonWriter, value: Snowflake, encoderContext: EncoderContext) {
+ writer.writeString(value.toString())
+ }
+
+ override fun getEncoderClass(): Class =
+ Snowflake::class.java
+}
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/StorageTypeCodec.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/StorageTypeCodec.kt
new file mode 100644
index 0000000000..e5eec71477
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/codecs/StorageTypeCodec.kt
@@ -0,0 +1,57 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.adapters.mongodb.codecs
+
+import com.kotlindiscord.kord.extensions.storage.StorageType
+import org.bson.BsonReader
+import org.bson.BsonWriter
+import org.bson.codecs.Codec
+import org.bson.codecs.DecoderContext
+import org.bson.codecs.EncoderContext
+
+public object StorageTypeCodec : Codec {
+ override fun decode(reader: BsonReader, decoderContext: DecoderContext): StorageType =
+ when (val string = reader.readString()) {
+ StorageType.Config.type -> StorageType.Config
+ StorageType.Data.type -> StorageType.Data
+
+ else -> error("Unknown storage type: $string")
+ }
+
+ override fun encode(writer: BsonWriter, value: StorageType, encoderContext: EncoderContext) {
+ writer.writeString(value.type)
+ }
+
+ override fun getEncoderClass(): Class =
+ StorageType::class.java
+}
+
+public object ConfigStorageTypeCodec : Codec {
+ override fun decode(reader: BsonReader, decoderContext: DecoderContext): StorageType.Config {
+ error("Do not use `StorageType.Config` as the type for a value - use `StorageType` instead.")
+ }
+
+ override fun encode(writer: BsonWriter, value: StorageType.Config, encoderContext: EncoderContext) {
+ StorageTypeCodec.encode(writer, value, encoderContext)
+ }
+
+ override fun getEncoderClass(): Class =
+ StorageType.Config::class.java
+}
+
+public object DataStorageTypeCodec : Codec {
+ override fun decode(reader: BsonReader, decoderContext: DecoderContext): StorageType.Data {
+ error("Do not use `StorageType.Data` as the type for a value - use `StorageType` instead.")
+ }
+
+ override fun encode(writer: BsonWriter, value: StorageType.Data, encoderContext: EncoderContext) {
+ StorageTypeCodec.encode(writer, value, encoderContext)
+ }
+
+ override fun getEncoderClass(): Class =
+ StorageType.Data::class.java
+}
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/AdaptedData.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/AdaptedData.kt
new file mode 100644
index 0000000000..248bbdd682
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/AdaptedData.kt
@@ -0,0 +1,30 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.adapters.mongodb.db
+
+import com.kotlindiscord.kord.extensions.storage.StorageType
+import dev.kord.common.entity.Snowflake
+import kotlinx.serialization.Serializable
+import org.bson.codecs.pojo.annotations.BsonId
+
+@Serializable
+@Suppress("ConstructorParameterNaming", "DataClassShouldBeImmutable")
+internal data class AdaptedData(
+ @BsonId
+ override val _id: String,
+
+ val identifier: String,
+
+ val type: StorageType? = null,
+
+ val channel: Snowflake? = null,
+ val guild: Snowflake? = null,
+ val message: Snowflake? = null,
+ val user: Snowflake? = null,
+
+ var data: String,
+) : Entity
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/Database.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/Database.kt
new file mode 100644
index 0000000000..b568a223d6
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/Database.kt
@@ -0,0 +1,47 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.adapters.mongodb.db
+
+import com.kotlindiscord.kord.extensions.adapters.mongodb.MONGODB_URI
+import com.kotlindiscord.kord.extensions.adapters.mongodb.kordExCodecRegistry
+import com.mongodb.MongoClientSettings
+import com.mongodb.MongoException
+import com.mongodb.kotlin.client.coroutine.MongoClient
+import com.mongodb.kotlin.client.coroutine.MongoCollection
+import com.mongodb.kotlin.client.coroutine.MongoDatabase
+import io.github.oshai.kotlinlogging.KotlinLogging
+import org.bson.BsonInt64
+import org.bson.Document
+import org.bson.codecs.configuration.CodecRegistries
+
+internal object Database {
+ private val logger = KotlinLogging.logger {}
+ private val client = MongoClient.create(MONGODB_URI)
+
+ private val codecRegistry = CodecRegistries.fromRegistries(
+ kordExCodecRegistry,
+ MongoClientSettings.getDefaultCodecRegistry(),
+ )
+
+ val db: MongoDatabase = client.getDatabase("kordex-data")
+
+ @Throws(MongoException::class)
+ suspend fun setup() {
+ val command = Document("ping", BsonInt64(1))
+
+ db.runCommand(command)
+
+ logger.info { "Connected to database." }
+
+ Migrations.migrate()
+ }
+
+ inline fun getCollection(name: String): MongoCollection =
+ db
+ .getCollection(name)
+ .withCodecRegistry(codecRegistry)
+}
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/Entity.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/Entity.kt
new file mode 100644
index 0000000000..4ec2eda1ac
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/Entity.kt
@@ -0,0 +1,12 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.adapters.mongodb.db
+
+@Suppress("VariableNaming", "PropertyName")
+internal interface Entity {
+ val _id: ID
+}
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/Metadata.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/Metadata.kt
new file mode 100644
index 0000000000..f7aca2a3ee
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/Metadata.kt
@@ -0,0 +1,57 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.adapters.mongodb.db
+
+import com.mongodb.client.model.Filters.eq
+import com.mongodb.client.model.ReplaceOptions
+import com.mongodb.client.result.UpdateResult
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.serialization.Serializable
+import org.bson.codecs.pojo.annotations.BsonId
+
+@Serializable
+@Suppress("DataClassContainsFunctions", "DataClassShouldBeImmutable")
+internal data class Metadata(
+ @BsonId
+ override val _id: String,
+
+ var version: Int,
+) : Entity {
+ suspend inline fun save(): UpdateResult =
+ Companion.save(this)
+
+ companion object {
+ const val COLLECTION_NAME: String = "metadata"
+
+ private val Filters = object {
+ fun byId(id: String) =
+ eq(Metadata::_id.name, id)
+ }
+
+ private val collection by lazy {
+ Database.getCollection(COLLECTION_NAME)
+ }
+
+ suspend fun get(id: String): Int? =
+ collection
+ .find(Filters.byId(id))
+ .limit(1)
+ .firstOrNull()
+ ?.version
+
+ suspend fun set(id: String, version: Int): UpdateResult =
+ save(Metadata(id, version))
+
+ suspend fun save(document: Metadata): UpdateResult =
+ collection
+ .replaceOne(
+ Filters.byId(document._id),
+ document,
+ ReplaceOptions().upsert(true)
+ )
+ }
+}
diff --git a/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/Migrations.kt b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/Migrations.kt
new file mode 100644
index 0000000000..03ec3c4cf6
--- /dev/null
+++ b/data-adapters/adapter-mongodb/src/main/kotlin/com/kotlindiscord/kord/extensions/adapters/mongodb/db/Migrations.kt
@@ -0,0 +1,40 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.adapters.mongodb.db
+
+import com.mongodb.kotlin.client.coroutine.MongoDatabase
+import io.github.oshai.kotlinlogging.KotlinLogging
+
+private const val META_NAME: String = "data-adapter"
+
+private val migrations = mutableMapOf Unit>(
+ 1 to { it.createCollection(Metadata.COLLECTION_NAME) },
+)
+
+internal object Migrations {
+ private val logger = KotlinLogging.logger {}
+
+ suspend fun migrate() {
+ logger.info { "Running migrations..." }
+
+ var adapterVersion = Metadata.get(META_NAME)
+ ?: 0
+
+ val latestVersion = migrations.keys.max()
+
+ while (adapterVersion < latestVersion) {
+ adapterVersion += 1
+
+ logger.debug { "Migrating: $META_NAME v${adapterVersion - 1} -> $adapterVersion" }
+
+ migrations[adapterVersion]!!(Database.db)
+ Metadata.set(META_NAME, adapterVersion)
+ }
+
+ logger.info { "Finished migrating database." }
+ }
+}
diff --git a/detekt.yml b/detekt.yml
index 592bb8b4ee..f440e69d9e 100644
--- a/detekt.yml
+++ b/detekt.yml
@@ -569,7 +569,7 @@ style:
NewLineAtEndOfFile:
active: true
NoTabs:
- active: true
+ active: false
OptionalAbstractKeyword:
active: true
OptionalUnit:
diff --git a/extra-modules/extra-mappings/build.gradle.kts b/extra-modules/extra-mappings/build.gradle.kts
index f9fad8c5b3..272d59ca98 100644
--- a/extra-modules/extra-mappings/build.gradle.kts
+++ b/extra-modules/extra-mappings/build.gradle.kts
@@ -49,7 +49,7 @@ dependencies {
detektPlugins(libs.detekt)
detektPlugins(libs.detekt.libraries)
- implementation(libs.logging)
+ implementation(libs.bundles.logging)
implementation(libs.kotlin.stdlib)
testImplementation(libs.groovy) // For logback config
diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/Checks.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/Checks.kt
index a4cda668e7..2898845009 100644
--- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/Checks.kt
+++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/Checks.kt
@@ -13,7 +13,7 @@ import dev.kord.common.entity.Snowflake
import dev.kord.core.entity.channel.CategorizableChannel
import dev.kord.core.entity.channel.GuildChannel
import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
/**
* Check ensuring that a command was sent within an allowed category, or not within a banned one.
diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt
index 169d577166..2a76311940 100644
--- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt
+++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt
@@ -11,7 +11,7 @@
"UndocumentedPublicProperty",
)
-@file:OptIn(DelicateCoroutinesApi::class)
+@file:OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class)
package com.kotlindiscord.kord.extensions.modules.extra.mappings
@@ -47,13 +47,14 @@ import com.kotlindiscord.kord.extensions.types.respond
import dev.kord.common.entity.Permission
import dev.kord.core.behavior.GuildBehavior
import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent
+import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.withContext
import me.shedaniel.linkie.*
import me.shedaniel.linkie.namespaces.*
import me.shedaniel.linkie.utils.*
-import mu.KotlinLogging
import kotlin.collections.set
import kotlin.error
import kotlin.io.path.Path
@@ -231,6 +232,32 @@ class MappingsExtension : Extension() {
}
}
+ // region: Barn mappings lookups
+
+ if (barnEnabled) {
+ slashCommand(
+ "barn",
+ "Barn",
+ BarnNamespace,
+ ::BarnArguments
+ )
+ }
+
+ // endregion
+
+ // region: Feather mappings lookups
+
+ if (featherEnabled) {
+ slashCommand(
+ "feather",
+ "Feather",
+ FeatherNamespace,
+ ::FeatherArguments
+ )
+ }
+
+ // endregion
+
// region: Legacy Yarn mappings lookups
slashCommand(
@@ -406,6 +433,19 @@ class MappingsExtension : Extension() {
// endregion
+ // region: SRG Mojang mappings lookups
+
+ if (srgMojangEnabled) {
+ slashCommand(
+ "srg",
+ "SRG Mojang",
+ MojangSrgNamespace,
+ ::SrgMojangArguments
+ )
+ }
+
+ // endregion
+
// region: Yarn mappings lookups
slashCommand(
diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/BarnArguments.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/BarnArguments.kt
new file mode 100644
index 0000000000..3d8d8e5a88
--- /dev/null
+++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/BarnArguments.kt
@@ -0,0 +1,20 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments
+
+import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean
+import me.shedaniel.linkie.namespaces.BarnNamespace
+
+/** Arguments for Barn mappings lookup commands. **/
+@Suppress("UndocumentedPublicProperty")
+class BarnArguments : MappingArguments(BarnNamespace), IntermediaryMappable {
+ override val mapDescriptors by defaultingBoolean {
+ name = "map-descriptor"
+ description = "Whether to map field/method descriptors to named instead of intermediary"
+ defaultValue = true
+ }
+}
diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/FeatherArguments.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/FeatherArguments.kt
new file mode 100644
index 0000000000..568c594a3f
--- /dev/null
+++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/FeatherArguments.kt
@@ -0,0 +1,20 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments
+
+import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean
+import me.shedaniel.linkie.namespaces.FeatherNamespace
+
+/** Arguments for Feather mappings lookup commands. **/
+@Suppress("UndocumentedPublicProperty")
+class FeatherArguments : MappingArguments(FeatherNamespace), IntermediaryMappable {
+ override val mapDescriptors by defaultingBoolean {
+ name = "map-descriptor"
+ description = "Whether to map field/method descriptors to named instead of Calamus"
+ defaultValue = true
+ }
+}
diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/SrgMojangArguments.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/SrgMojangArguments.kt
new file mode 100644
index 0000000000..ca6563efaa
--- /dev/null
+++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/SrgMojangArguments.kt
@@ -0,0 +1,20 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments
+
+import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean
+import me.shedaniel.linkie.namespaces.MojangSrgNamespace
+
+/** Arguments for SRG Mojang mappings lookup commands. **/
+@Suppress("UndocumentedPublicProperty")
+class SrgMojangArguments : MappingArguments(MojangSrgNamespace), IntermediaryMappable {
+ override val mapDescriptors by defaultingBoolean {
+ name = "map-descriptor"
+ description = "Whether to map field/method descriptors to named instead of SRG"
+ defaultValue = true
+ }
+}
diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt
index 0e48e70383..b69bc7544d 100644
--- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt
+++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt
@@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
-@file:OptIn(DelicateCoroutinesApi::class)
+@file:OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class)
package com.kotlindiscord.kord.extensions.modules.extra.mappings.converters
@@ -21,6 +21,7 @@ import dev.kord.core.entity.interaction.StringOptionValue
import dev.kord.rest.builder.interaction.OptionsBuilder
import dev.kord.rest.builder.interaction.StringChoiceBuilder
import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.withContext
import me.shedaniel.linkie.MappingsContainer
diff --git a/extra-modules/extra-phishing/build.gradle.kts b/extra-modules/extra-phishing/build.gradle.kts
index 69c1bd002c..d26eeeea78 100644
--- a/extra-modules/extra-phishing/build.gradle.kts
+++ b/extra-modules/extra-phishing/build.gradle.kts
@@ -25,7 +25,7 @@ dependencies {
implementation(libs.jsoup)
- implementation(libs.logging)
+ implementation(libs.bundles.logging)
implementation(libs.kotlin.stdlib)
implementation(libs.ktor.logging)
diff --git a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingExtension.kt b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingExtension.kt
index 57bac3db6e..eef0732a39 100644
--- a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingExtension.kt
+++ b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingExtension.kt
@@ -29,6 +29,7 @@ import dev.kord.core.entity.channel.GuildMessageChannel
import dev.kord.core.event.message.MessageCreateEvent
import dev.kord.core.event.message.MessageUpdateEvent
import dev.kord.rest.builder.message.create.embed
+import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
@@ -38,7 +39,6 @@ import io.ktor.utils.io.jvm.javaio.*
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.lastOrNull
import kotlinx.coroutines.launch
-import mu.KotlinLogging
import org.jsoup.Jsoup
/** The maximum number of redirects to attempt to follow for a URL. **/
diff --git a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingWebsocketWrapper.kt b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingWebsocketWrapper.kt
index 65f19a2824..d41ae03085 100644
--- a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingWebsocketWrapper.kt
+++ b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingWebsocketWrapper.kt
@@ -10,8 +10,8 @@ package com.kotlindiscord.kord.extensions.modules.extra.phishing
import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
import dev.kord.core.Kord
+import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.client.*
-import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.websocket.*
import io.ktor.client.request.*
@@ -22,9 +22,7 @@ import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
-import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
-import mu.KotlinLogging
import org.koin.core.component.inject
/**
@@ -35,74 +33,74 @@ import org.koin.core.component.inject
* @property callback Callback to invoke with domain changes
*/
class PhishingWebsocketWrapper(
- private val appName: String,
- private val callback: suspend (DomainChange) -> Unit,
+ private val appName: String,
+ private val callback: suspend (DomainChange) -> Unit,
) : KordExKoinComponent {
- private val logger = KotlinLogging.logger { }
- private var job: Job? = null
-
- private val kord: Kord by inject()
-
- internal val client = HttpClient {
- install(ContentNegotiation) {
- json()
- }
-
- install(WebSockets)
-
- expectSuccess = true
- }
-
- /**
- * Connect the websocket, and start processing incoming data. This will also stop any current websocket connection.
- */
- suspend fun start() {
- stop()
-
- job = kord.launch {
- while (true) {
- try {
- websocket()
- } catch (e: ClosedReceiveChannelException) {
- logger.info { "Websocket closed by the server." }
- } catch (e: Exception) {
- logger.error(e) { "Exception thrown during webhook connection/processing." }
- }
-
- logger.info { "Reconnecting..." }
-
- delay(1000)
- }
- }
- }
-
- /** If the websocket is connected, disconnect by killing its job. **/
- fun stop() {
- job?.cancel()
- job = null
- }
-
- private suspend fun websocket() {
- client.webSocket(
- "wss://phish.sinking.yachts/feed",
- { header("X-Identity", "$appName (via Kord Extensions)") }
- ) {
- logger.info { "Websocket connected." }
-
- while (isActive) {
- val frame = incoming.receive() as Frame.Text
- val frameText = frame.readText()
-
- logger.debug { "Sinking Yachts <<< $frameText" }
-
- try {
- val change: DomainChange = Json.decodeFromString(frameText)
-
- callback(change)
- } catch (e: Exception) {
- logger.error(e) { "Failed to handle incoming domain change." }
- }
- }
- }
- }
+ private val logger = KotlinLogging.logger { }
+ private var job: Job? = null
+
+ private val kord: Kord by inject()
+
+ internal val client = HttpClient {
+ install(ContentNegotiation) {
+ json()
+ }
+
+ install(WebSockets)
+
+ expectSuccess = true
+ }
+
+ /**
+ * Connect the websocket, and start processing incoming data. This will also stop any current websocket connection.
+ */
+ suspend fun start() {
+ stop()
+
+ job = kord.launch {
+ while (true) {
+ try {
+ websocket()
+ } catch (e: ClosedReceiveChannelException) {
+ logger.info { "Websocket closed by the server." }
+ } catch (e: Exception) {
+ logger.error(e) { "Exception thrown during webhook connection/processing." }
+ }
+
+ logger.info { "Reconnecting..." }
+
+ delay(1000)
+ }
+ }
+ }
+
+ /** If the websocket is connected, disconnect by killing its job. **/
+ fun stop() {
+ job?.cancel()
+ job = null
+ }
+
+ private suspend fun websocket() {
+ client.webSocket(
+ "wss://phish.sinking.yachts/feed",
+ { header("X-Identity", "$appName (via Kord Extensions)") }
+ ) {
+ logger.info { "Websocket connected." }
+
+ while (isActive) {
+ val frame = incoming.receive() as Frame.Text
+ val frameText = frame.readText()
+
+ logger.debug { "Sinking Yachts <<< $frameText" }
+
+ try {
+ val change: DomainChange = Json.decodeFromString(frameText)
+
+ callback(change)
+ } catch (e: Exception) {
+ logger.error(e) { "Failed to handle incoming domain change." }
+ }
+ }
+ }
+ }
}
diff --git a/extra-modules/extra-pluralkit/build.gradle.kts b/extra-modules/extra-pluralkit/build.gradle.kts
index e19c426c8e..ae08bbff21 100644
--- a/extra-modules/extra-pluralkit/build.gradle.kts
+++ b/extra-modules/extra-pluralkit/build.gradle.kts
@@ -23,7 +23,7 @@ dependencies {
detektPlugins(libs.detekt)
detektPlugins(libs.detekt.libraries)
- implementation(libs.logging)
+ implementation(libs.bundles.logging)
implementation(libs.kotlin.stdlib)
implementation(libs.ktor.logging)
diff --git a/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/PKExtension.kt b/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/PKExtension.kt
index fcc6f8117b..27e4776142 100644
--- a/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/PKExtension.kt
+++ b/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/PKExtension.kt
@@ -46,13 +46,13 @@ import dev.kord.core.event.message.MessageDeleteEvent
import dev.kord.core.event.message.MessageUpdateEvent
import dev.kord.rest.builder.message.create.embed
import dev.kord.rest.request.KtorRequestException
+import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.datetime.Clock
-import mu.KotlinLogging
import kotlin.time.Duration.Companion.seconds
const val NEGATIVE_EMOTE = "❌"
diff --git a/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/api/PluralKit.kt b/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/api/PluralKit.kt
index e6c8a8d1fc..66113bace2 100644
--- a/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/api/PluralKit.kt
+++ b/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/api/PluralKit.kt
@@ -10,6 +10,7 @@ package com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api
import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.utils.LRUHashMap
import dev.kord.common.entity.Snowflake
+import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.*
@@ -18,7 +19,6 @@ import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
-import mu.KotlinLogging
internal const val PK_API_VERSION = 2
diff --git a/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/events/PKMessageDeleteEvent.kt b/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/events/PKMessageDeleteEvent.kt
index 5e76b1b30d..10b9b06c00 100644
--- a/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/events/PKMessageDeleteEvent.kt
+++ b/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/events/PKMessageDeleteEvent.kt
@@ -65,7 +65,7 @@ abstract class PKMessageDeleteEvent(
override suspend fun getGuild(): Guild = getGuildOrNull()!!
override suspend fun getGuildOrNull(): Guild? = guildId?.let { supplier.getGuildOrNull(it) }
- override suspend fun getMember(): Member = author!!
+ override suspend fun getMember(): Member = author!!
override suspend fun getMemberOrNull(): Member? = author
override suspend fun getMessage(): Message = message!!
diff --git a/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/events/PKMessageUpdateEvent.kt b/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/events/PKMessageUpdateEvent.kt
index e73e89bdd6..70bee8ede5 100644
--- a/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/events/PKMessageUpdateEvent.kt
+++ b/extra-modules/extra-pluralkit/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/pluralkit/events/PKMessageUpdateEvent.kt
@@ -67,7 +67,7 @@ abstract class PKMessageUpdateEvent(
override suspend fun getGuild(): Guild = getGuildOrNull()!!
override suspend fun getGuildOrNull(): Guild? = member?.guild?.asGuildOrNull()
- override suspend fun getMember(): Member = author!!
+ override suspend fun getMember(): Member = author!!
override suspend fun getMemberOrNull(): Member? = author
override suspend fun getMessage(): Message =
diff --git a/extra-modules/extra-pluralkit/src/main/resources/translations/kordex/pluralkit_ko.properties b/extra-modules/extra-pluralkit/src/main/resources/translations/kordex/pluralkit_ko.properties
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/extra-modules/extra-pluralkit/src/main/resources/translations/kordex/pluralkit_ko.properties
@@ -0,0 +1 @@
+
diff --git a/extra-modules/extra-pluralkit/src/main/resources/translations/kordex/pluralkit_pt.properties b/extra-modules/extra-pluralkit/src/main/resources/translations/kordex/pluralkit_pt.properties
new file mode 100644
index 0000000000..128beb071d
--- /dev/null
+++ b/extra-modules/extra-pluralkit/src/main/resources/translations/kordex/pluralkit_pt.properties
@@ -0,0 +1,28 @@
+
+
+argument.bot.name=bot
+argument.bot.description=Seleciona uma instância de PK alternativa, se necessário
+command.pluralkit.description=Gere e vê as definições da integração de PluralKit neste servidor
+command.pluralkit.api-url.name=api-url
+command.pluralkit.api-url.response.current=**URL da API atual:** `{0}`
+command.pluralkit.api-url.response.reset=**URL da API predefinido:** `{0}`
+command.pluralkit.api-url.response.updated=**URL da API atualizado:** `{0}`
+command.pluralkit.bot.name=bot
+command.pluralkit.bot.description=Seleciona uma instância de PluralKit alternativa, se tiveres uma
+command.pluralkit.bot.response.current=**Instância de PK atual:** {0}
+command.pluralkit.bot.response.updated=**Instância de PK atualizada:** {0}
+command.pluralkit.status.name=estado
+command.pluralkit.status.description=Confere as definições da integração de PluralKit neste servidor
+command.pluralkit.status.response.description=**URL da API:** `{0}`\n**Instância de PK:** {1}\n**Ativada:** {2}
+command.pluralkit.toggle-support.name=alternar
+command.pluralkit.toggle-support.description=Desativa ou ativa a integração de PluralKit como necessário
+argument.api-url.name=api-url
+argument.api-url.description=Define um URL alternativo para a API, ou "redefinir" para usar a predefinição
+arguments.reset=redefinir
+argument.toggle.name=ativar
+argument.toggle.description=Definir se a integração de PK deve ser usada neste servidor
+command.pluralkit.name=pluralkit
+command.pluralkit.api-url.description=Define um URL alternativo para a API, ou "redefinir" para usar a predefinição
+command.pluralkit.status.response.title=Definições da integração de PluralKit
+command.pluralkit.toggle-support.response.current=**Desativada:** {0}
+command.pluralkit.toggle-support.response.updated=**Alternada:** {0}
diff --git a/gradle.properties b/gradle.properties
index 169cc8002a..edeb68eb82 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,7 +3,7 @@ kotlin.incremental = true
ksp.incremental = false
-projectVersion = 1.5.8-SNAPSHOT
+projectVersion = 1.5.9-SNAPSHOT
#dokka will run out of memory with the default meta space
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1024m
diff --git a/libs.versions.toml b/gradle/libs.versions.toml
similarity index 69%
rename from libs.versions.toml
rename to gradle/libs.versions.toml
index 9e63b23d98..856bb9ebe4 100644
--- a/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,31 +1,33 @@
[versions]
-detekt = "1.22.0" # Note: Plugin versions must be updated in buildSrc/build.gradle.kts
-dokka = "1.8.10" # Note: Plugin versions must be updated in buildSrc/build.gradle.kts
-kotlin = "1.8.20" # Note: Plugin versions must be updated in buildSrc/build.gradle.kts
+detekt = "1.23.1" # Note: Plugin versions must be updated in buildSrc/build.gradle.kts
+dokka = "1.9.0" # Note: Plugin versions must be updated in buildSrc/build.gradle.kts
+kotlin = "1.9.10" # Note: Plugin versions must be updated in buildSrc/build.gradle.kts
commons-validator = "1.7"
-groovy = "3.0.14"
+groovy = "3.0.19"
icu4j = "73.2"
jansi = "2.4.0"
-jsoup = "1.15.4"
-junit = "5.9.3"
-koin = "3.4.0"
+jsoup = "1.16.1"
+junit = "5.10.0"
+koin = "3.5.0"
konf = "1.1.2"
#kord = "0.9.x-SNAPSHOT"
-kord = "0.10.0-SNAPSHOT"
-kotlinpoet = "1.13.0"
-ksp = "1.8.20-1.0.11"
-ktor = "2.2.4"
-kx-ser = "1.5.0"
-linkie = "1.0.99"
-logback = "1.4.5"
-logback-groovy = "1.14.0"
-logging = "2.1.23"
-pf4j = "3.9.0"
-sentry = "6.17.0"
-time4j-base = "5.9.2"
+kord = "0.11.0-SNAPSHOT"
+ksp = "1.9.10-1.0.13"
+ktor = "2.3.4"
+kx-coro = "1.7.3"
+kx-ser = "1.6.0"
+linkie = "1.0.114"
+logback = "1.4.11"
+logback-groovy = "1.14.5"
+logging = "5.1.0"
+mongodb = "4.10.2"
+pf4j = "3.10.0"
+sentry = "6.29.0"
+slf4j = "2.0.9"
+time4j-base = "5.9.3"
time4j-tzdata = "5.0-2023b"
-toml = "0.1.8"
+toml = "0.2.0"
[libraries]
commons-validator = { module = "commons-validator:commons-validator", version.ref = "commons-validator" }
@@ -43,20 +45,23 @@ koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" }
kord = { module = "dev.kord:kord-core-voice", version.ref = "kord" }
-kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" }
ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
ktor-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
+kx-coro = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kx-coro"}
kx-ser = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kx-ser" }
linkie = { module = "me.shedaniel:linkie-core", version.ref = "linkie" }
logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
logback-groovy = { module = "io.github.virtualdogbert:logback-groovy-config", version.ref = "logback-groovy" }
-logging = { module = "io.github.microutils:kotlin-logging", version.ref = "logging" }
+kotlin-logging = { module = "io.github.oshai:kotlin-logging", version.ref = "logging" }
+mongodb = { module = "org.mongodb:mongodb-driver-kotlin-coroutine", version.ref = "mongodb" }
pf4j = { module = "org.pf4j:pf4j", version.ref = "pf4j" }
sentry = { module = "io.sentry:sentry", version.ref = "sentry" }
+slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
time4j-base = { module = "net.time4j:time4j-base", version.ref = "time4j-base" }
time4j-tzdata = { module = "net.time4j:time4j-tzdata", version.ref = "time4j-tzdata" }
toml = { module = "net.peanuuutz:tomlkt", version.ref = "toml" }
[bundles]
commons = [ "commons-validator" ]
+logging = [ "kotlin-logging", "slf4j" ]
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index c1962a79e2..7f93135c49 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 8707e8b506..ac72c34e8a 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index aeb74cbb43..0adc8e1a53 100755
--- a/gradlew
+++ b/gradlew
@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -130,10 +131,13 @@ location of your Java installation."
fi
else
JAVACMD=java
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
diff --git a/kord-extensions/build.gradle.kts b/kord-extensions/build.gradle.kts
index 70601fb101..82c62904c0 100644
--- a/kord-extensions/build.gradle.kts
+++ b/kord-extensions/build.gradle.kts
@@ -29,7 +29,7 @@ dependencies {
api(libs.kord)
- api(libs.logging) // Basic logging setup
+ api(libs.bundles.logging) // Basic logging setup
api(libs.kx.ser)
api(libs.sentry) // Needs to be transitive or bots will start breaking
api(libs.toml)
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt
index 83a52affd9..69969b8a3c 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt
@@ -39,14 +39,14 @@ import dev.kord.core.gateway.handler.DefaultGatewayEventInterceptor
import dev.kord.core.on
import dev.kord.gateway.Intents
import dev.kord.gateway.PrivilegedIntent
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement
-import mu.KLogger
-import mu.KotlinLogging
import org.koin.core.component.inject
import org.koin.dsl.bind
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt
index f8690d0d7a..c8233f3cfa 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt
@@ -11,7 +11,10 @@ package com.kotlindiscord.kord.extensions.builders
import com.kotlindiscord.kord.extensions.DISCORD_BLURPLE
import com.kotlindiscord.kord.extensions.ExtensibleBot
import com.kotlindiscord.kord.extensions.annotations.BotBuilderDSL
-import com.kotlindiscord.kord.extensions.checks.types.*
+import com.kotlindiscord.kord.extensions.checks.types.ChatCommandCheck
+import com.kotlindiscord.kord.extensions.checks.types.MessageCommandCheck
+import com.kotlindiscord.kord.extensions.checks.types.SlashCommandCheck
+import com.kotlindiscord.kord.extensions.checks.types.UserCommandCheck
import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandRegistry
import com.kotlindiscord.kord.extensions.commands.application.DefaultApplicationCommandRegistry
import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry
@@ -48,14 +51,15 @@ import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
import dev.kord.gateway.Intent
import dev.kord.gateway.Intents
+import dev.kord.gateway.NON_PRIVILEGED
import dev.kord.gateway.PrivilegedIntent
import dev.kord.gateway.builder.PresenceBuilder
import dev.kord.gateway.builder.Shards
import dev.kord.rest.builder.message.create.MessageCreateBuilder
import dev.kord.rest.builder.message.create.allowedMentions
-import io.ktor.utils.io.*
-import mu.KLogger
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
+import org.koin.core.annotation.KoinInternalApi
import org.koin.core.logger.Level
import org.koin.dsl.bind
import org.koin.fileProperties
@@ -125,8 +129,8 @@ public open class ExtensibleBotBuilder {
public val pluginBuilder: PluginBuilder = PluginBuilder(this)
/** @suppress Builder that shouldn't be set directly by the user. **/
- public var intentsBuilder: (Intents.IntentsBuilder.() -> Unit)? = {
- +Intents.nonPrivileged
+ public var intentsBuilder: (Intents.Builder.() -> Unit)? = {
+ +Intents.NON_PRIVILEGED
if (chatCommandsBuilder.enabled) {
+Intent.MessageContent
@@ -294,11 +298,11 @@ public open class ExtensibleBotBuilder {
public fun intents(
addDefaultIntents: Boolean = true,
addExtensionIntents: Boolean = true,
- builder: Intents.IntentsBuilder.() -> Unit
+ builder: Intents.Builder.() -> Unit
) {
this.intentsBuilder = {
if (addDefaultIntents) {
- +Intents.nonPrivileged
+ +Intents.NON_PRIVILEGED
if (chatCommandsBuilder.enabled) {
+Intent.MessageContent
@@ -369,6 +373,7 @@ public open class ExtensibleBotBuilder {
}
/** @suppress Creates a new KoinApplication if it has not already been started. **/
+ @OptIn(KoinInternalApi::class)
private fun startKoinIfNeeded() {
var logLevel = koinLogLevel
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelChecks.kt
index c078198db2..6a2b42da1b 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelChecks.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelChecks.kt
@@ -12,8 +12,8 @@ import dev.kord.core.behavior.channel.CategoryBehavior
import dev.kord.core.behavior.channel.ChannelBehavior
import dev.kord.core.entity.channel.Category
import dev.kord.core.event.Event
+import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.flow.toList
-import mu.KotlinLogging
// region: Entity DSL versions
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelTypeChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelTypeChecks.kt
index cced733daf..4b467e3576 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelTypeChecks.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelTypeChecks.kt
@@ -10,7 +10,7 @@ import com.kotlindiscord.kord.extensions.checks.types.CheckContext
import com.kotlindiscord.kord.extensions.utils.translate
import dev.kord.common.entity.ChannelType
import dev.kord.core.event.Event
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
/**
* Check asserting that the channel an [Event] fired in is of a given set of types.
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt
index e6c066b61b..ad45b9db4f 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt
@@ -31,6 +31,7 @@ import dev.kord.core.event.role.RoleDeleteEvent
import dev.kord.core.event.role.RoleUpdateEvent
import dev.kord.core.event.user.PresenceUpdateEvent
import dev.kord.core.event.user.UserUpdateEvent
+import dev.kord.core.event.user.VoiceStateUpdateEvent
import kotlinx.coroutines.flow.first
/**
@@ -64,6 +65,7 @@ public suspend fun channelFor(event: Event): ChannelBehavior? {
is ReactionRemoveEmojiEvent -> event.channel
is ReactionRemoveEvent -> event.channel
is TypingStartEvent -> event.channel
+ is VoiceStateUpdateEvent -> event.kord.unsafe.channel(event.state.channelId ?: return null)
is WebhookUpdateEvent -> event.channel
is ThreadChannelDeleteEvent -> event.old
@@ -128,6 +130,7 @@ public suspend fun channelIdFor(event: Event): ULong? {
is ReactionRemoveEmojiEvent -> event.channel.id.value
is ReactionRemoveEvent -> event.channel.id.value
is TypingStartEvent -> event.channel.id.value
+ is VoiceStateUpdateEvent -> event.state.channelId?.value
is WebhookUpdateEvent -> event.channel.id.value
is ThreadChannelDeleteEvent -> event.channel.id.value
@@ -170,6 +173,7 @@ public suspend fun channelSnowflakeFor(event: Event): Snowflake? {
is ReactionRemoveEmojiEvent -> event.channel.id
is ReactionRemoveEvent -> event.channel.id
is TypingStartEvent -> event.channel.id
+ is VoiceStateUpdateEvent -> event.state.channelId
is WebhookUpdateEvent -> event.channel.id
is ThreadChannelDeleteEvent -> event.channel.id
@@ -252,6 +256,7 @@ public suspend fun guildFor(event: Event): GuildBehavior? {
is VoiceChannelDeleteEvent -> event.channel.guild
is VoiceChannelUpdateEvent -> event.channel.guild
is VoiceServerUpdateEvent -> event.guild
+ is VoiceStateUpdateEvent -> event.state.getGuildOrNull()
is WebhookUpdateEvent -> event.guild
is ThreadChannelCreateEvent -> event.channel.guild
@@ -324,6 +329,11 @@ public suspend fun memberFor(event: Event): MemberBehavior? {
event.member.asMember(thread.guildId)
}
+ is VoiceStateUpdateEvent -> event.kord.unsafe.member(
+ event.state.guildId,
+ event.state.userId
+ )
+
// event is ThreadMembersUpdateEvent -> event.
else -> null
}
@@ -443,6 +453,8 @@ public suspend fun userFor(event: Event): UserBehavior? {
is TypingStartEvent -> event.user
is UserUpdateEvent -> event.user
+ is VoiceStateUpdateEvent -> event.kord.unsafe.user(event.state.userId)
+
is ThreadChannelCreateEvent -> event.channel.owner
// is ThreadUpdateEvent -> event.
// is ThreadChannelDeleteEvent -> event.
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/GuildChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/GuildChecks.kt
index 944246b81b..0af82297d5 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/GuildChecks.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/GuildChecks.kt
@@ -10,7 +10,7 @@ import com.kotlindiscord.kord.extensions.checks.types.CheckContext
import dev.kord.common.entity.Snowflake
import dev.kord.core.behavior.GuildBehavior
import dev.kord.core.event.Event
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
/**
* Check asserting an [Event] was fired within a guild.
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MemberChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MemberChecks.kt
index bf18d914d9..35d167af03 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MemberChecks.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MemberChecks.kt
@@ -15,7 +15,7 @@ import dev.kord.common.entity.Permission
import dev.kord.common.entity.Permissions
import dev.kord.core.entity.channel.GuildChannel
import dev.kord.core.event.Event
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
/**
* Check asserting that the user an [Event] fired for has a given permission, or the Administrator permission.
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MiscChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MiscChecks.kt
index 119ce23821..fb082ee456 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MiscChecks.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MiscChecks.kt
@@ -9,7 +9,7 @@ package com.kotlindiscord.kord.extensions.checks
import com.kotlindiscord.kord.extensions.checks.types.CheckContext
import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior
import dev.kord.core.event.Event
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
/**
* Check asserting the user for an [Event] is a bot. Will fail if the event doesn't concern a user.
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/NSFWChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/NSFWChecks.kt
index 6f67ef7ac4..3843308478 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/NSFWChecks.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/NSFWChecks.kt
@@ -14,7 +14,7 @@ import com.kotlindiscord.kord.extensions.utils.translate
import dev.kord.common.entity.ChannelType
import dev.kord.common.entity.NsfwLevel
import dev.kord.core.event.Event
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
/**
* Check asserting an [Event] was fired within a guild with the given NSFW level.
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/RoleChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/RoleChecks.kt
index 5c1f2a36a1..f515383373 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/RoleChecks.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/RoleChecks.kt
@@ -13,8 +13,8 @@ import com.kotlindiscord.kord.extensions.utils.getTopRole
import dev.kord.common.entity.Snowflake
import dev.kord.core.behavior.RoleBehavior
import dev.kord.core.event.Event
+import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.flow.toList
-import mu.KotlinLogging
// region: Entity DSL versions
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt
index abbb1418d8..1cfa456e03 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt
@@ -11,7 +11,7 @@ import dev.kord.common.entity.Snowflake
import dev.kord.core.behavior.channel.ChannelBehavior
import dev.kord.core.entity.channel.thread.ThreadChannel
import dev.kord.core.event.Event
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
// region: Entity DSL versions
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/_Logging.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/_Logging.kt
index b02a65e73a..83a77155d5 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/_Logging.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/_Logging.kt
@@ -10,7 +10,7 @@ package com.kotlindiscord.kord.extensions.checks
import dev.kord.common.entity.Snowflake
import dev.kord.core.event.Event
-import mu.KLogger
+import io.github.oshai.kotlinlogging.KLogger
/** Convenience wrapper for a "failing check" log message. **/
public inline fun KLogger.failed(reason: String): Unit =
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandRegistry.kt
index 67b29e1b95..b0a4699fde 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandRegistry.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandRegistry.kt
@@ -43,8 +43,8 @@ import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent
import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent
import dev.kord.rest.builder.interaction.*
import dev.kord.rest.request.KtorRequestException
-import mu.KLogger
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
import java.util.*
import javax.naming.InvalidNameException
@@ -585,7 +585,6 @@ public abstract class ApplicationCommandRegistry : KordExKoinComponent {
is Choice.NumberChoice -> Choice.NumberChoice(name, Optional(nameLocalizations), it.value)
is Choice.StringChoice -> Choice.StringChoice(name, Optional(nameLocalizations), it.value)
is Choice.IntegerChoice -> Choice.IntegerChoice(name, Optional(nameLocalizations), it.value)
- is Choice.IntChoice -> Choice.IntegerChoice(name, Optional(nameLocalizations), it.value)
}
}.toMutableList()
}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt
index 5155b8d43b..e77546270d 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt
@@ -22,8 +22,8 @@ import dev.kord.common.entity.ApplicationCommandType
import dev.kord.core.entity.channel.DmChannel
import dev.kord.core.entity.channel.GuildMessageChannel
import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent
-import mu.KLogger
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
/**
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt
index fbb8d8b310..a7f317e8c8 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt
@@ -30,8 +30,8 @@ import dev.kord.core.entity.interaction.InteractionCommand
import dev.kord.core.entity.interaction.SubCommand
import dev.kord.core.event.interaction.AutoCompleteInteractionCreateEvent
import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent
-import mu.KLogger
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
/**
@@ -136,6 +136,21 @@ public abstract class SlashCommand, A : Argumen
throw InvalidCommandException(name, "No command action or subcommands/groups given.")
}
+ val subCommandWithSubCommand = if (parentCommand != null && subCommands.isNotEmpty()) {
+ this
+ } else {
+ subCommands.firstOrNull { it.subCommands.isNotEmpty() }
+ }
+
+ if (subCommandWithSubCommand != null) {
+ throw InvalidCommandException(
+ parentCommand?.name ?: name,
+
+ "Subcommand ${subCommandWithSubCommand.name} has its own subcommands, but subcommands may not be " +
+ "nested."
+ )
+ }
+
if (::body.isInitialized && !(groups.isEmpty() && subCommands.isEmpty())) {
throw InvalidCommandException(
name,
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt
index 1db0e1c735..c49e087730 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt
@@ -18,7 +18,7 @@ import com.kotlindiscord.kord.extensions.commands.converters.*
import com.kotlindiscord.kord.extensions.commands.getDefaultTranslatedDisplayName
import dev.kord.core.entity.interaction.OptionValue
import dev.kord.core.entity.interaction.StringOptionValue
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger {}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt
index 8139194d54..b63c029759 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt
@@ -10,8 +10,8 @@ import com.kotlindiscord.kord.extensions.InvalidCommandException
import com.kotlindiscord.kord.extensions.commands.application.Localized
import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider
import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
-import mu.KLogger
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
import java.util.*
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/EnumChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/EnumChoiceConverter.kt
index 17440a6d51..98c6665d98 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/EnumChoiceConverter.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/EnumChoiceConverter.kt
@@ -58,7 +58,6 @@ import dev.kord.rest.builder.interaction.StringChoiceBuilder
functionSuffixedWhere = "E : Enum, E : ChoiceEnum"
)
-
public class EnumChoiceConverter(
typeName: String,
private val getter: suspend (String) -> E?,
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt
index 267e76e6ea..03a7d37893 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt
@@ -32,7 +32,6 @@ private const val DEFAULT_RADIX = 10
types = [ConverterType.CHOICE, ConverterType.DEFAULTING, ConverterType.OPTIONAL, ConverterType.SINGLE],
builderFields = ["public var radix: Int = $DEFAULT_RADIX"]
)
-
public class NumberChoiceConverter(
private val radix: Int = DEFAULT_RADIX,
choices: Map,
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/StringChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/StringChoiceConverter.kt
index 8ebb260cb2..98dbfe5f19 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/StringChoiceConverter.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/StringChoiceConverter.kt
@@ -26,7 +26,6 @@ import dev.kord.rest.builder.interaction.StringChoiceBuilder
types = [ConverterType.CHOICE, ConverterType.DEFAULTING, ConverterType.OPTIONAL, ConverterType.SINGLE]
)
-
public class StringChoiceConverter(
choices: Map,
override var validator: Validator = null
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt
index 48c105eb7d..5659ab6965 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt
@@ -22,8 +22,8 @@ import dev.kord.common.entity.ApplicationCommandType
import dev.kord.core.entity.channel.DmChannel
import dev.kord.core.entity.channel.GuildMessageChannel
import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent
-import mu.KLogger
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
/**
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt
index 023581eb2d..3bf313ac9a 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt
@@ -30,7 +30,7 @@ import dev.kord.common.entity.Permission
import dev.kord.core.entity.channel.DmChannel
import dev.kord.core.entity.channel.GuildMessageChannel
import dev.kord.core.event.message.MessageCreateEvent
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
import java.util.*
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt
index 52d6c3e9c9..aa604ab15b 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt
@@ -23,7 +23,7 @@ import com.kotlindiscord.kord.extensions.commands.converters.*
import com.kotlindiscord.kord.extensions.commands.getDefaultTranslatedDisplayName
import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider
import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
import java.util.*
import kotlin.collections.set
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt
index 6a43a88373..d19589afac 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt
@@ -16,7 +16,7 @@ import com.kotlindiscord.kord.extensions.parser.StringParser
import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
import com.kotlindiscord.kord.extensions.utils.getLocale
import dev.kord.core.event.message.MessageCreateEvent
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
import java.util.*
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/BooleanConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/BooleanConverter.kt
index f3badac707..e2c5d86b0a 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/BooleanConverter.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/BooleanConverter.kt
@@ -30,7 +30,6 @@ import dev.kord.rest.builder.interaction.OptionsBuilder
types = [ConverterType.DEFAULTING, ConverterType.LIST, ConverterType.OPTIONAL, ConverterType.SINGLE]
)
-
public class BooleanConverter(
override var validator: Validator = null
) : SingleConverter() {
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt
index 74640d3fe8..9896c76c49 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt
@@ -5,7 +5,7 @@
*/
@file:OptIn(
- FlowPreview::class,
+ ExperimentalCoroutinesApi::class,
)
package com.kotlindiscord.kord.extensions.commands.converters.impl
@@ -29,7 +29,7 @@ import dev.kord.core.entity.interaction.OptionValue
import dev.kord.core.event.interaction.AutoCompleteInteractionCreateEvent
import dev.kord.rest.builder.interaction.ChannelBuilder
import dev.kord.rest.builder.interaction.OptionsBuilder
-import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.toList
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt
index 5d286e6e7e..d7627fe026 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt
@@ -24,8 +24,8 @@ import dev.kord.core.entity.interaction.OptionValue
import dev.kord.core.entity.interaction.StringOptionValue
import dev.kord.rest.builder.interaction.OptionsBuilder
import dev.kord.rest.builder.interaction.StringChoiceBuilder
+import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.datetime.*
-import mu.KotlinLogging
/**
* Argument converter for Kotlin [DateTimePeriod] arguments. You can apply these to an `Instant` using `plus` and a
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt
index f1de8dbef4..e8f0c3d307 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt
@@ -28,7 +28,7 @@ import dev.kord.core.entity.interaction.StringOptionValue
import dev.kord.core.exception.EntityNotFoundException
import dev.kord.rest.builder.interaction.OptionsBuilder
import dev.kord.rest.builder.interaction.StringChoiceBuilder
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger {}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt
index 1b99076e8f..e1b3d85e70 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt
@@ -15,8 +15,8 @@ import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler
import dev.kord.core.event.interaction.ButtonInteractionCreateEvent
import dev.kord.core.event.interaction.ModalSubmitInteractionCreateEvent
import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
-import mu.KLogger
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
/**
* Component registry, keeps track of components and handles incoming interaction events, dispatching as needed to
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt
index 5ef4993a8c..7ce93d7375 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt
@@ -20,9 +20,9 @@ import dev.kord.common.entity.Permission
import dev.kord.core.behavior.channel.asChannelOf
import dev.kord.core.entity.channel.GuildChannel
import dev.kord.core.event.interaction.ComponentInteractionCreateEvent
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.sync.Mutex
-import mu.KLogger
-import mu.KotlinLogging
import org.koin.core.component.inject
/**
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt
index 98a094dd09..fd58c55c4d 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt
@@ -11,8 +11,14 @@ import com.kotlindiscord.kord.extensions.components.buttons.EphemeralInteraction
import com.kotlindiscord.kord.extensions.components.buttons.LinkInteractionButton
import com.kotlindiscord.kord.extensions.components.buttons.PublicInteractionButton
import com.kotlindiscord.kord.extensions.components.forms.ModalForm
-import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu
-import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu
+import com.kotlindiscord.kord.extensions.components.menus.channel.EphemeralChannelSelectMenu
+import com.kotlindiscord.kord.extensions.components.menus.channel.PublicChannelSelectMenu
+import com.kotlindiscord.kord.extensions.components.menus.role.EphemeralRoleSelectMenu
+import com.kotlindiscord.kord.extensions.components.menus.role.PublicRoleSelectMenu
+import com.kotlindiscord.kord.extensions.components.menus.string.EphemeralStringSelectMenu
+import com.kotlindiscord.kord.extensions.components.menus.string.PublicStringSelectMenu
+import com.kotlindiscord.kord.extensions.components.menus.user.EphemeralUserSelectMenu
+import com.kotlindiscord.kord.extensions.components.menus.user.PublicUserSelectMenu
import dev.kord.rest.builder.message.create.MessageCreateBuilder
import dev.kord.rest.builder.message.modify.MessageModifyBuilder
import kotlin.time.Duration
@@ -20,7 +26,7 @@ import kotlin.time.Duration
/** DSL function for creating a disabled button and adding it to the current [ComponentContainer]. **/
public suspend fun ComponentContainer.disabledButton(
row: Int? = null,
- builder: suspend DisabledInteractionButton.() -> Unit
+ builder: suspend DisabledInteractionButton.() -> Unit,
): DisabledInteractionButton {
val component = DisabledInteractionButton()
@@ -33,7 +39,7 @@ public suspend fun ComponentContainer.disabledButton(
/** DSL function for creating an ephemeral button and adding it to the current [ComponentContainer]. **/
public suspend fun ComponentContainer.ephemeralButton(
row: Int? = null,
- builder: suspend EphemeralInteractionButton.() -> Unit
+ builder: suspend EphemeralInteractionButton.() -> Unit,
): EphemeralInteractionButton {
val component = EphemeralInteractionButton(timeoutTask)
@@ -47,7 +53,7 @@ public suspend fun ComponentContainer.ephemeralButton(
public suspend fun ComponentContainer.ephemeralButton(
modal: (() -> M)?,
row: Int? = null,
- builder: suspend EphemeralInteractionButton.() -> Unit
+ builder: suspend EphemeralInteractionButton.() -> Unit,
): EphemeralInteractionButton {
val component = EphemeralInteractionButton(timeoutTask, modal)
@@ -60,7 +66,7 @@ public suspend fun ComponentContainer.ephemeralButton(
/** DSL function for creating a link button and adding it to the current [ComponentContainer]. **/
public suspend fun ComponentContainer.linkButton(
row: Int? = null,
- builder: suspend LinkInteractionButton.() -> Unit
+ builder: suspend LinkInteractionButton.() -> Unit,
): LinkInteractionButton {
val component = LinkInteractionButton()
@@ -73,7 +79,7 @@ public suspend fun ComponentContainer.linkButton(
/** DSL function for creating a public button and adding it to the current [ComponentContainer]. **/
public suspend fun ComponentContainer.publicButton(
row: Int? = null,
- builder: suspend PublicInteractionButton.() -> Unit
+ builder: suspend PublicInteractionButton.() -> Unit,
): PublicInteractionButton {
val component = PublicInteractionButton(timeoutTask)
@@ -87,7 +93,7 @@ public suspend fun ComponentContainer.publicButton(
public suspend fun ComponentContainer.publicButton(
modal: (() -> M)?,
row: Int? = null,
- builder: suspend PublicInteractionButton.() -> Unit
+ builder: suspend PublicInteractionButton.() -> Unit,
): PublicInteractionButton {
val component = PublicInteractionButton(timeoutTask, modal)
@@ -97,12 +103,33 @@ public suspend fun ComponentContainer.publicButton(
return component
}
-/** DSL function for creating an ephemeral select menu and adding it to the current [ComponentContainer]. **/
+/** DSL function for creating an ephemeral string select menu and adding it to the current [ComponentContainer]. **/
+@Deprecated(
+ message = "Deprecated to allow other option types.",
+ replaceWith = ReplaceWith("this.ephemeralStringSelectMenu(row, builder)")
+)
public suspend fun ComponentContainer.ephemeralSelectMenu(
row: Int? = null,
- builder: suspend EphemeralSelectMenu.() -> Unit
-): EphemeralSelectMenu {
- val component = EphemeralSelectMenu(timeoutTask)
+ builder: suspend EphemeralStringSelectMenu.() -> Unit,
+): EphemeralStringSelectMenu = ephemeralStringSelectMenu(row, builder)
+
+/** DSL function for creating an ephemeral string select menu and adding it to the current [ComponentContainer]. **/
+@Deprecated(
+ message = "Deprecated to allow other option types.",
+ replaceWith = ReplaceWith("this.ephemeralStringSelectMenu(modal, row, builder)")
+)
+public suspend fun ComponentContainer.ephemeralSelectMenu(
+ modal: (() -> M)?,
+ row: Int? = null,
+ builder: suspend EphemeralStringSelectMenu.() -> Unit,
+): EphemeralStringSelectMenu = ephemeralStringSelectMenu(modal, row, builder)
+
+/** DSL function for creating an ephemeral string select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.ephemeralStringSelectMenu(
+ row: Int? = null,
+ builder: suspend EphemeralStringSelectMenu.() -> Unit,
+): EphemeralStringSelectMenu {
+ val component = EphemeralStringSelectMenu(timeoutTask)
builder(component)
add(component, row)
@@ -110,13 +137,13 @@ public suspend fun ComponentContainer.ephemeralSelectMenu(
return component
}
-/** DSL function for creating an ephemeral select menu and adding it to the current [ComponentContainer]. **/
-public suspend fun ComponentContainer.ephemeralSelectMenu(
+/** DSL function for creating an ephemeral string select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.ephemeralStringSelectMenu(
modal: (() -> M)?,
row: Int? = null,
- builder: suspend EphemeralSelectMenu.() -> Unit
-): EphemeralSelectMenu {
- val component = EphemeralSelectMenu(timeoutTask, modal)
+ builder: suspend EphemeralStringSelectMenu.() -> Unit,
+): EphemeralStringSelectMenu {
+ val component = EphemeralStringSelectMenu(timeoutTask, modal)
builder(component)
add(component, row)
@@ -124,12 +151,33 @@ public suspend fun ComponentContainer.ephemeralSelectMenu(
return component
}
-/** DSL function for creating a public select menu and adding it to the current [ComponentContainer]. **/
+/** DSL function for creating a public string select menu and adding it to the current [ComponentContainer]. **/
+@Deprecated(
+ message = "Deprecated to allow other option types.",
+ replaceWith = ReplaceWith("this.publicStringSelectMenu(row, builder)")
+)
public suspend fun ComponentContainer.publicSelectMenu(
row: Int? = null,
- builder: suspend PublicSelectMenu.() -> Unit
-): PublicSelectMenu {
- val component = PublicSelectMenu(timeoutTask)
+ builder: suspend PublicStringSelectMenu.() -> Unit,
+): PublicStringSelectMenu = publicStringSelectMenu(row, builder)
+
+/** DSL function for creating a public string select menu and adding it to the current [ComponentContainer]. **/
+@Deprecated(
+ message = "Deprecated to allow other option types.",
+ replaceWith = ReplaceWith("this.publicStringSelectMenu(modal, row, builder)")
+)
+public suspend fun ComponentContainer.publicSelectMenu(
+ modal: (() -> M)?,
+ row: Int? = null,
+ builder: suspend PublicStringSelectMenu.() -> Unit,
+): PublicStringSelectMenu = publicStringSelectMenu(modal, row, builder)
+
+/** DSL function for creating a public string select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.publicStringSelectMenu(
+ row: Int? = null,
+ builder: suspend PublicStringSelectMenu.() -> Unit,
+): PublicStringSelectMenu {
+ val component = PublicStringSelectMenu(timeoutTask)
builder(component)
add(component, row)
@@ -137,13 +185,175 @@ public suspend fun ComponentContainer.publicSelectMenu(
return component
}
-/** DSL function for creating a public select menu and adding it to the current [ComponentContainer]. **/
-public suspend fun ComponentContainer.publicSelectMenu(
+/** DSL function for creating a public string select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.publicStringSelectMenu(
+ modal: (() -> M)?,
+ row: Int? = null,
+ builder: suspend PublicStringSelectMenu.() -> Unit,
+): PublicStringSelectMenu {
+ val component = PublicStringSelectMenu(timeoutTask, modal)
+
+ builder(component)
+ add(component, row)
+
+ return component
+}
+
+/** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.ephemeralUserSelectMenu(
+ row: Int? = null,
+ builder: suspend EphemeralUserSelectMenu.() -> Unit,
+): EphemeralUserSelectMenu {
+ val component = EphemeralUserSelectMenu(timeoutTask)
+
+ builder(component)
+ add(component, row)
+
+ return component
+}
+
+/** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.ephemeralUserSelectMenu(
+ modal: (() -> M)?,
+ row: Int? = null,
+ builder: suspend EphemeralUserSelectMenu.() -> Unit,
+): EphemeralUserSelectMenu {
+ val component = EphemeralUserSelectMenu(timeoutTask, modal)
+
+ builder(component)
+ add(component, row)
+
+ return component
+}
+
+/** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.publicUserSelectMenu(
+ row: Int? = null,
+ builder: suspend PublicUserSelectMenu.() -> Unit,
+): PublicUserSelectMenu {
+ val component = PublicUserSelectMenu(timeoutTask)
+
+ builder(component)
+ add(component, row)
+
+ return component
+}
+
+/** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.publicUserSelectMenu(
+ modal: (() -> M)?,
+ row: Int? = null,
+ builder: suspend PublicUserSelectMenu.() -> Unit,
+): PublicUserSelectMenu {
+ val component = PublicUserSelectMenu(timeoutTask, modal)
+
+ builder(component)
+ add(component, row)
+
+ return component
+}
+
+/** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.ephemeralRoleSelectMenu(
+ row: Int? = null,
+ builder: suspend EphemeralRoleSelectMenu.() -> Unit,
+): EphemeralRoleSelectMenu {
+ val component = EphemeralRoleSelectMenu(timeoutTask)
+
+ builder(component)
+ add(component, row)
+
+ return component
+}
+
+/** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.ephemeralRoleSelectMenu(
+ modal: (() -> M)?,
+ row: Int? = null,
+ builder: suspend EphemeralRoleSelectMenu.() -> Unit,
+): EphemeralRoleSelectMenu {
+ val component = EphemeralRoleSelectMenu(timeoutTask, modal)
+
+ builder(component)
+ add(component, row)
+
+ return component
+}
+
+/** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.publicRoleSelectMenu(
+ row: Int? = null,
+ builder: suspend PublicRoleSelectMenu.() -> Unit,
+): PublicRoleSelectMenu {
+ val component = PublicRoleSelectMenu(timeoutTask)
+
+ builder(component)
+ add(component, row)
+
+ return component
+}
+
+/** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.publicRoleSelectMenu(
+ modal: (() -> M)?,
+ row: Int? = null,
+ builder: suspend PublicRoleSelectMenu.() -> Unit,
+): PublicRoleSelectMenu {
+ val component = PublicRoleSelectMenu(timeoutTask, modal)
+
+ builder(component)
+ add(component, row)
+
+ return component
+}
+
+/** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.ephemeralChannelSelectMenu(
+ row: Int? = null,
+ builder: suspend EphemeralChannelSelectMenu.() -> Unit,
+): EphemeralChannelSelectMenu {
+ val component = EphemeralChannelSelectMenu(timeoutTask)
+
+ builder(component)
+ add(component, row)
+
+ return component
+}
+
+/** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.ephemeralChannelSelectMenu(
+ modal: (() -> M)?,
+ row: Int? = null,
+ builder: suspend EphemeralChannelSelectMenu.() -> Unit,
+): EphemeralChannelSelectMenu {
+ val component = EphemeralChannelSelectMenu(timeoutTask, modal)
+
+ builder(component)
+ add(component, row)
+
+ return component
+}
+
+/** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.publicChannelSelectMenu(
+ row: Int? = null,
+ builder: suspend PublicChannelSelectMenu.() -> Unit,
+): PublicChannelSelectMenu {
+ val component = PublicChannelSelectMenu(timeoutTask)
+
+ builder(component)
+ add(component, row)
+
+ return component
+}
+
+/** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/
+public suspend fun ComponentContainer.publicChannelSelectMenu(
modal: (() -> M)?,
row: Int? = null,
- builder: suspend PublicSelectMenu.() -> Unit
-): PublicSelectMenu {
- val component = PublicSelectMenu(timeoutTask, modal)
+ builder: suspend PublicChannelSelectMenu.() -> Unit,
+): PublicChannelSelectMenu {
+ val component = PublicChannelSelectMenu(timeoutTask, modal)
builder(component)
add(component, row)
@@ -172,7 +382,7 @@ public suspend fun MessageModifyBuilder.applyComponents(components: ComponentCon
*/
public suspend fun MessageCreateBuilder.components(
timeout: Duration? = null,
- builder: suspend ComponentContainer.() -> Unit
+ builder: suspend ComponentContainer.() -> Unit,
): ComponentContainer {
val container = ComponentContainer(timeout, true, builder)
@@ -188,7 +398,7 @@ public suspend fun MessageCreateBuilder.components(
*/
public suspend fun MessageModifyBuilder.components(
timeout: Duration? = null,
- builder: suspend ComponentContainer.() -> Unit
+ builder: suspend ComponentContainer.() -> Unit,
): ComponentContainer {
val container = ComponentContainer(timeout, true, builder)
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt
index 434bee620c..1529ef23dc 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt
@@ -17,9 +17,9 @@ import com.kotlindiscord.kord.extensions.utils.scheduling.Task
import dev.kord.common.entity.DiscordPartialEmoji
import dev.kord.core.entity.channel.DmChannel
import dev.kord.core.event.interaction.ButtonInteractionCreateEvent
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
import io.sentry.Sentry
-import mu.KLogger
-import mu.KotlinLogging
/** Abstract class representing a button component that has a click action. **/
public abstract class InteractionButtonWithAction(timeoutTask: Task?) :
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt
index d572430421..4c6ff3067d 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt
@@ -4,13 +4,12 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
-@file:Suppress("TooGenericExceptionCaught")
-@file:OptIn(KordUnsafe::class)
-
package com.kotlindiscord.kord.extensions.components.menus
import com.kotlindiscord.kord.extensions.DiscordRelayedException
import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.components.menus.channel.InitialEphemeralSelectMenuResponseBuilder
+import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext
import com.kotlindiscord.kord.extensions.types.FailureReason
import com.kotlindiscord.kord.extensions.types.respond
import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
@@ -19,19 +18,16 @@ import com.kotlindiscord.kord.extensions.utils.scheduling.Task
import dev.kord.common.annotation.KordUnsafe
import dev.kord.core.behavior.interaction.modal
import dev.kord.core.behavior.interaction.respondEphemeral
+import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior
import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
-import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder
-
-public typealias InitialEphemeralSelectMenuResponseBuilder =
- (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)?
/** Class representing an ephemeral-only select (dropdown) menu. **/
-public open class EphemeralSelectMenu(
+public abstract class EphemeralSelectMenu(
timeoutTask: Task?,
public override val modal: (() -> M)? = null,
-) : SelectMenu, M>(timeoutTask) {
- /** @suppress Initial response builder. **/
- public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null
+ /** Builder for the initial response, omit to ack instead. **/
+ public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null,
+) : SelectMenu(timeoutTask) where C : SelectMenuContext, C : EphemeralInteractionContext {
/** Call this to open with a response, omit it to ack instead. **/
public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) {
@@ -46,6 +42,15 @@ public open class EphemeralSelectMenu(
}
}
+ /** Function to create the context of the select menu. **/
+ public abstract fun createContext(
+ event: SelectMenuInteractionCreateEvent,
+ interactionResponse: EphemeralMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+ ): C
+
+ @OptIn(KordUnsafe::class)
+ @Suppress("TooGenericExceptionCaught")
override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock {
val cache: MutableStringKeyedMap = mutableMapOf()
@@ -96,7 +101,7 @@ public open class EphemeralSelectMenu(
}
}
- val context = EphemeralSelectMenuContext(this, event, response, cache)
+ val context = createContext(event, response, cache)
context.populate()
@@ -120,9 +125,9 @@ public open class EphemeralSelectMenu(
}
override suspend fun respondText(
- context: EphemeralSelectMenuContext,
+ context: C,
message: String,
- failureType: FailureReason<*>
+ failureType: FailureReason<*>,
) {
context.respond { settings.failureResponseBuilder(this, message, failureType) }
}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt
index 4ef24a0333..29b603b0c5 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt
@@ -4,14 +4,13 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
-@file:Suppress("TooGenericExceptionCaught")
-@file:OptIn(KordUnsafe::class)
-
package com.kotlindiscord.kord.extensions.components.menus
import com.kotlindiscord.kord.extensions.DiscordRelayedException
import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.components.menus.channel.InitialEphemeralSelectMenuResponseBuilder
import com.kotlindiscord.kord.extensions.types.FailureReason
+import com.kotlindiscord.kord.extensions.types.PublicInteractionContext
import com.kotlindiscord.kord.extensions.types.respond
import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
import com.kotlindiscord.kord.extensions.utils.getLocale
@@ -20,22 +19,19 @@ import dev.kord.common.annotation.KordUnsafe
import dev.kord.core.behavior.interaction.modal
import dev.kord.core.behavior.interaction.respondEphemeral
import dev.kord.core.behavior.interaction.respondPublic
+import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior
import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
-import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder
-
-public typealias InitialPublicSelectMenuResponseBuilder =
- (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)?
/** Class representing a public-only select (dropdown) menu. **/
-public open class PublicSelectMenu(
+public abstract class PublicSelectMenu(
timeoutTask: Task?,
public override val modal: (() -> M)? = null,
-) : SelectMenu, M>(timeoutTask) {
- /** @suppress Initial response builder. **/
- public open var initialResponseBuilder: InitialPublicSelectMenuResponseBuilder = null
+ /** The initial response builder, omit to ack instead. **/
+ public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null,
+) : SelectMenu(timeoutTask) where C : SelectMenuContext, C : PublicInteractionContext {
/** Call this to open with a response, omit it to ack instead. **/
- public fun initialResponse(body: InitialPublicSelectMenuResponseBuilder) {
+ public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) {
initialResponseBuilder = body
}
@@ -47,6 +43,15 @@ public open class PublicSelectMenu(
}
}
+ /** Function to create the context for the select menu. **/
+ public abstract fun createContext(
+ event: SelectMenuInteractionCreateEvent,
+ interactionResponse: PublicMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+ ): C
+
+ @OptIn(KordUnsafe::class)
+ @Suppress("TooGenericExceptionCaught")
override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock {
val cache: MutableStringKeyedMap = mutableMapOf()
@@ -97,7 +102,7 @@ public open class PublicSelectMenu(
}
}
- val context = PublicSelectMenuContext(this, event, response, cache)
+ val context = createContext(event, response, cache)
context.populate()
@@ -121,9 +126,9 @@ public open class PublicSelectMenu(
}
override suspend fun respondText(
- context: PublicSelectMenuContext,
+ context: C,
message: String,
- failureType: FailureReason<*>
+ failureType: FailureReason<*>,
) {
context.respond { settings.failureResponseBuilder(this, message, failureType) }
}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt
index e22bdcca87..1aa21ebf9c 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt
@@ -15,9 +15,6 @@ import com.kotlindiscord.kord.extensions.types.FailureReason
import com.kotlindiscord.kord.extensions.utils.scheduling.Task
import dev.kord.core.entity.channel.DmChannel
import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
-import dev.kord.rest.builder.component.ActionRowBuilder
-import dev.kord.rest.builder.component.SelectOptionBuilder
-import dev.kord.rest.builder.component.options
import io.sentry.Sentry
import mu.KLogger
import mu.KotlinLogging
@@ -39,158 +36,101 @@ public const val VALUE_MAX: Int = 100
/** Abstract class representing a select (dropdown) menu component. **/
public abstract class SelectMenu(
- timeoutTask: Task?,
+ timeoutTask: Task?,
) : ComponentWithAction(timeoutTask) {
- internal val logger: KLogger = KotlinLogging.logger {}
+ internal val logger: KLogger = KotlinLogging.logger {}
- /** List of options for the user to choose from. **/
- public val options: MutableList = mutableListOf()
-
- /** The minimum number of choices that the user must make. **/
- public var minimumChoices: Int = 1
-
- /** The maximum number of choices that the user can make. Set to `null` for no maximum. **/
- public var maximumChoices: Int? = 1
+ /** The minimum number of choices that the user must make. **/
+ public var minimumChoices: Int = 1
- /** Placeholder text to show before the user has selected any options. **/
- public var placeholder: String? = null
+ /** The maximum number of choices that the user can make. Set to `null` for no maximum. **/
+ public var maximumChoices: Int? = 1
- @Suppress("MagicNumber") // WHY DO YOU THINK I ASSIGN IT HERE
- override val unitWidth: Int = 5
+ /** Placeholder text to show before the user has selected any options. **/
+ public var placeholder: String? = null
- /** Whether this select menu is disabled. **/
- public open var disabled: Boolean? = null
-
- /** Mark this select menu as disabled. **/
- public open fun disable() {
- disabled = true
- }
+ @Suppress("MagicNumber") // WHY DO YOU THINK I ASSIGN IT HERE
+ override val unitWidth: Int = 5
- /** Mark this select menu as enabled. **/
- public open fun enable() {
- disabled = null // Don't ask me why this is
- }
+ /** Whether this select menu is disabled. **/
+ public open var disabled: Boolean? = null
- /** Add an option to this select menu. **/
- @Suppress("UnnecessaryParentheses") // Disagrees with IDEA, amusingly.
- public open suspend fun option(
- label: String,
- value: String,
+ /** Mark this select menu as disabled. **/
+ public open fun disable() {
+ disabled = true
+ }
- // TODO: Check this is fixed in later versions of the compiler
- // This is nullable like this due to a compiler bug: https://youtrack.jetbrains.com/issue/KT-51820
- body: (suspend SelectOptionBuilder.() -> Unit)? = null,
- ) {
- val builder = SelectOptionBuilder(label, value)
+ /** Mark this select menu as enabled. **/
+ public open fun enable() {
+ disabled = null // Don't ask me why this is
+ }
- if (body != null) {
- body(builder)
- }
+ /** If enabled, adds the initial Sentry breadcrumb to the given context. **/
+ public open suspend fun firstSentryBreadcrumb(context: C, button: SelectMenu<*, *>) {
+ if (sentry.enabled) {
+ context.sentry.breadcrumb(BreadcrumbType.User) {
+ category = "component.selectMenu"
+ message = "Select menu \"${button.id}\" submitted."
- if ((builder.description?.length ?: 0) > DESCRIPTION_MAX) {
- error("Option descriptions must not be longer than $DESCRIPTION_MAX characters.")
- }
+ data["component"] = button.id
+ context.addContextDataToBreadcrumb(this)
+ }
+ }
+ }
- if (builder.label.length > LABEL_MAX) {
- error("Option labels must not be longer than $LABEL_MAX characters.")
- }
-
- if (builder.value.length > VALUE_MAX) {
- error("Option values must not be longer than $VALUE_MAX characters.")
- }
-
- options.add(builder)
- }
-
- public override fun apply(builder: ActionRowBuilder) {
- if (maximumChoices == null || maximumChoices!! > options.size) {
- maximumChoices = options.size
- }
-
- builder.stringSelect(id) {
- this.allowedValues = minimumChoices..maximumChoices!!
-
- this.options.addAll(this@SelectMenu.options)
- this.placeholder = this@SelectMenu.placeholder
-
- this.disabled = this@SelectMenu.disabled
- }
- }
-
- @Suppress("UnnecessaryParentheses") // Disagrees with IDEA, amusingly.
- override fun validate() {
- super.validate()
-
- if (this.options.isEmpty()) {
- error("Menu components must have at least one option.")
- }
-
- if (this.options.size > OPTIONS_MAX) {
- error("Menu components must not have more than $OPTIONS_MAX options.")
- }
-
- if ((this.placeholder?.length ?: 0) > PLACEHOLDER_MAX) {
- error("Menu components must not have a placeholder longer than $PLACEHOLDER_MAX characters.")
- }
- }
-
- /** If enabled, adds the initial Sentry breadcrumb to the given context. **/
- public open suspend fun firstSentryBreadcrumb(context: C, button: SelectMenu<*, *>) {
- if (sentry.enabled) {
- context.sentry.breadcrumb(BreadcrumbType.User) {
- category = "component.selectMenu"
- message = "Select menu \"${button.id}\" submitted."
-
- data["component"] = button.id
- context.addContextDataToBreadcrumb(this)
- }
- }
- }
-
- /** A general way to handle errors thrown during the course of a select menu action's execution. **/
- public open suspend fun handleError(context: C, t: Throwable, button: SelectMenu<*, *>) {
- logger.error(t) { "Error during execution of select menu (${button.id}) action (${context.event})" }
-
- if (sentry.enabled) {
- logger.trace { "Submitting error to sentry." }
-
- val channel = context.channel
- val author = context.user.asUserOrNull()
-
- val sentryId = context.sentry.captureException(t) {
- if (author != null) {
- user(author)
- }
-
- tag("private", "false")
-
- if (channel is DmChannel) {
- tag("private", "true")
- }
-
- tag("component", button.id)
-
- Sentry.captureException(t)
- }
-
- logger.info { "Error submitted to Sentry: $sentryId" }
-
- val errorMessage = if (bot.extensions.containsKey("sentry")) {
- context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId))
- } else {
- context.translate("commands.error.user", null)
- }
-
- respondText(context, errorMessage, FailureReason.ExecutionError(t))
- } else {
- respondText(
- context,
- context.translate("commands.error.user", null),
- FailureReason.ExecutionError(t)
- )
- }
- }
+ /** A general way to handle errors thrown during the course of a select menu action's execution. **/
+ public open suspend fun handleError(context: C, t: Throwable, button: SelectMenu<*, *>) {
+ logger.error(t) { "Error during execution of select menu (${button.id}) action (${context.event})" }
- /** Override this to implement a way to respond to the user, regardless of whatever happens. **/
- public abstract suspend fun respondText(context: C, message: String, failureType: FailureReason<*>)
+ if (sentry.enabled) {
+ logger.trace { "Submitting error to sentry." }
+
+ val channel = context.channel
+ val author = context.user.asUserOrNull()
+
+ val sentryId = context.sentry.captureException(t) {
+ if (author != null) {
+ user(author)
+ }
+
+ tag("private", "false")
+
+ if (channel is DmChannel) {
+ tag("private", "true")
+ }
+
+ tag("component", button.id)
+
+ Sentry.captureException(t)
+ }
+
+ logger.info { "Error submitted to Sentry: $sentryId" }
+
+ val errorMessage = if (bot.extensions.containsKey("sentry")) {
+ context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId))
+ } else {
+ context.translate("commands.error.user", null)
+ }
+
+ respondText(context, errorMessage, FailureReason.ExecutionError(t))
+ } else {
+ respondText(
+ context,
+ context.translate("commands.error.user", null),
+ FailureReason.ExecutionError(t)
+ )
+ }
+ }
+
+ @Suppress("UnnecessaryParentheses")
+ override fun validate() {
+ super.validate()
+
+ if ((this.placeholder?.length ?: 0) > PLACEHOLDER_MAX) {
+ error("Menu components must not have a placeholder longer than $PLACEHOLDER_MAX characters.")
+ }
+ }
+
+ /** Override this to implement a way to respond to the user, regardless of whatever happens. **/
+ public abstract suspend fun respondText(context: C, message: String, failureType: FailureReason<*>)
}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt
index 7b65dc24bb..1cca94e7d3 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt
@@ -6,17 +6,14 @@
package com.kotlindiscord.kord.extensions.components.menus
-import com.kotlindiscord.kord.extensions.components.Component
import com.kotlindiscord.kord.extensions.components.ComponentContext
import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
/** Abstract class representing the execution context of a select (dropdown) menu component. **/
+@Suppress("UnnecessaryAbstractClass")
public abstract class SelectMenuContext(
- component: Component,
+ component: SelectMenu<*, *>,
event: SelectMenuInteractionCreateEvent,
- cache: MutableStringKeyedMap
-) : ComponentContext(component, event, cache) {
- /** Menu options that were selected by the user before de-focusing the menu. **/
- public val selected: List by lazy { event.interaction.values }
-}
+ cache: MutableStringKeyedMap,
+) : ComponentContext(component, event, cache)
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt
new file mode 100644
index 0000000000..594558bec1
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt
@@ -0,0 +1,39 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.channel
+
+import com.kotlindiscord.kord.extensions.components.menus.OPTIONS_MAX
+import com.kotlindiscord.kord.extensions.components.menus.SelectMenu
+import dev.kord.common.entity.ChannelType
+import dev.kord.rest.builder.component.ActionRowBuilder
+
+/** Interface for channel select menus. **/
+public interface ChannelSelectMenu {
+ /** The types allowed in the select menu. **/
+ public var channelTypes: MutableList
+
+ /** Add an allowed channel type to the selector. **/
+ public fun channelType(vararg type: ChannelType) {
+ channelTypes.addAll(type)
+ }
+
+ /** Apply the channel select menu to an action row builder. **/
+ public fun applyChannelSelectMenu(selectMenu: SelectMenu<*, *>, builder: ActionRowBuilder) {
+ if (selectMenu.maximumChoices == null) selectMenu.maximumChoices = OPTIONS_MAX
+
+ builder.channelSelect(selectMenu.id) {
+ this.channelTypes = if (this@ChannelSelectMenu.channelTypes.isEmpty()) {
+ null
+ } else {
+ this@ChannelSelectMenu.channelTypes
+ }
+ this.allowedValues = selectMenu.minimumChoices..selectMenu.maximumChoices!!
+ this.placeholder = selectMenu.placeholder
+ this.disabled = selectMenu.disabled
+ }
+ }
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenuContext.kt
new file mode 100644
index 0000000000..acadd11d84
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenuContext.kt
@@ -0,0 +1,29 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.channel
+
+import com.kotlindiscord.kord.extensions.components.menus.SelectMenu
+import com.kotlindiscord.kord.extensions.components.menus.SelectMenuContext
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import dev.kord.common.annotation.KordExperimental
+import dev.kord.common.annotation.KordUnsafe
+import dev.kord.common.entity.Snowflake
+import dev.kord.core.behavior.channel.ChannelBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+
+/** Abstract class representing the execution context of a channel select (dropdown) menu component. **/
+public abstract class ChannelSelectMenuContext(
+ component: SelectMenu<*, *>,
+ event: SelectMenuInteractionCreateEvent,
+ cache: MutableStringKeyedMap,
+) : SelectMenuContext(component, event, cache) {
+ /** Menu options that were selected by the user before de-focusing the menu. **/
+ @OptIn(KordUnsafe::class, KordExperimental::class)
+ public val selected: List by lazy {
+ event.interaction.values.map { event.kord.unsafe.channel(Snowflake(it)) }
+ }
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenu.kt
new file mode 100644
index 0000000000..8209df30fc
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenu.kt
@@ -0,0 +1,38 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.channel
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import com.kotlindiscord.kord.extensions.utils.scheduling.Task
+import dev.kord.common.entity.ChannelType
+import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+import dev.kord.rest.builder.component.ActionRowBuilder
+import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder
+
+public typealias InitialEphemeralSelectMenuResponseBuilder =
+ (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)?
+
+/** Class representing an ephemeral-only channel select (dropdown) menu. **/
+public open class EphemeralChannelSelectMenu(
+ timeoutTask: Task?,
+ public override val modal: (() -> M)? = null,
+) : EphemeralSelectMenu, M>(timeoutTask), ChannelSelectMenu {
+ override var channelTypes: MutableList = mutableListOf()
+
+ override fun createContext(
+ event: SelectMenuInteractionCreateEvent,
+ interactionResponse: EphemeralMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+ ): EphemeralChannelSelectMenuContext = EphemeralChannelSelectMenuContext(
+ this, event, interactionResponse, cache
+ )
+
+ public override fun apply(builder: ActionRowBuilder): Unit = applyChannelSelectMenu(this, builder)
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenuContext.kt
new file mode 100644
index 0000000000..3363a560cb
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenuContext.kt
@@ -0,0 +1,21 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.channel
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+
+/** Class representing the execution context for an ephemeral-only channel select (dropdown) menu. **/
+public class EphemeralChannelSelectMenuContext(
+ override val component: EphemeralChannelSelectMenu,
+ override val event: SelectMenuInteractionCreateEvent,
+ override val interactionResponse: EphemeralMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+) : ChannelSelectMenuContext(component, event, cache), EphemeralInteractionContext
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenu.kt
new file mode 100644
index 0000000000..ca9b10bd27
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenu.kt
@@ -0,0 +1,38 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.channel
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import com.kotlindiscord.kord.extensions.utils.scheduling.Task
+import dev.kord.common.entity.ChannelType
+import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+import dev.kord.rest.builder.component.ActionRowBuilder
+import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder
+
+public typealias InitialPublicSelectMenuResponseBuilder =
+ (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)?
+
+/** Class representing a public-only channel select (dropdown) menu. **/
+public open class PublicChannelSelectMenu(
+ timeoutTask: Task?,
+ public override val modal: (() -> M)? = null,
+) : PublicSelectMenu, M>(timeoutTask), ChannelSelectMenu {
+ override var channelTypes: MutableList = mutableListOf()
+
+ override fun createContext(
+ event: SelectMenuInteractionCreateEvent,
+ interactionResponse: PublicMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+ ): PublicChannelSelectMenuContext = PublicChannelSelectMenuContext(
+ this, event, interactionResponse, cache
+ )
+
+ override fun apply(builder: ActionRowBuilder): Unit = applyChannelSelectMenu(this, builder)
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenuContext.kt
new file mode 100644
index 0000000000..8405d7291b
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenuContext.kt
@@ -0,0 +1,21 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.channel
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.types.PublicInteractionContext
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+
+/** Class representing the execution context for a public-only channel select (dropdown) menu. **/
+public class PublicChannelSelectMenuContext(
+ override val component: PublicChannelSelectMenu,
+ override val event: SelectMenuInteractionCreateEvent,
+ override val interactionResponse: PublicMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+) : ChannelSelectMenuContext(component, event, cache), PublicInteractionContext
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenu.kt
new file mode 100644
index 0000000000..e8d256db5d
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenu.kt
@@ -0,0 +1,35 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.role
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import com.kotlindiscord.kord.extensions.utils.scheduling.Task
+import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+import dev.kord.rest.builder.component.ActionRowBuilder
+import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder
+
+public typealias InitialEphemeralSelectMenuResponseBuilder =
+ (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)?
+
+/** Class representing an ephemeral-only role select (dropdown) menu. **/
+public open class EphemeralRoleSelectMenu(
+ timeoutTask: Task?,
+ public override val modal: (() -> M)? = null,
+) : EphemeralSelectMenu, M>(timeoutTask), RoleSelectMenu {
+ override fun createContext(
+ event: SelectMenuInteractionCreateEvent,
+ interactionResponse: EphemeralMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+ ): EphemeralRoleSelectMenuContext = EphemeralRoleSelectMenuContext(
+ this, event, interactionResponse, cache
+ )
+
+ override fun apply(builder: ActionRowBuilder): Unit = applyRoleSelectMenu(this, builder)
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenuContext.kt
similarity index 70%
rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt
rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenuContext.kt
index 00e46f02dd..7feb6482d7 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenuContext.kt
@@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
-package com.kotlindiscord.kord.extensions.components.menus
+package com.kotlindiscord.kord.extensions.components.menus.role
import com.kotlindiscord.kord.extensions.components.forms.ModalForm
import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext
@@ -12,10 +12,10 @@ import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior
import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
-/** Class representing the execution context for an ephemeral-only select (dropdown) menu. **/
-public class EphemeralSelectMenuContext(
- override val component: EphemeralSelectMenu,
+/** Class representing the execution context for an ephemeral-only role select (dropdown) menu. **/
+public class EphemeralRoleSelectMenuContext(
+ override val component: EphemeralRoleSelectMenu,
override val event: SelectMenuInteractionCreateEvent,
override val interactionResponse: EphemeralMessageInteractionResponseBehavior,
- cache: MutableStringKeyedMap
-) : SelectMenuContext(component, event, cache), EphemeralInteractionContext
+ cache: MutableStringKeyedMap,
+) : RoleSelectMenuContext(component, event, cache), EphemeralInteractionContext
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenu.kt
new file mode 100644
index 0000000000..f02658c8ec
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenu.kt
@@ -0,0 +1,35 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.role
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import com.kotlindiscord.kord.extensions.utils.scheduling.Task
+import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+import dev.kord.rest.builder.component.ActionRowBuilder
+import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder
+
+public typealias InitialPublicSelectMenuResponseBuilder =
+ (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)?
+
+/** Class representing a public-only role select (dropdown) menu. **/
+public open class PublicRoleSelectMenu(
+ timeoutTask: Task?,
+ public override val modal: (() -> M)? = null,
+) : PublicSelectMenu, M>(timeoutTask), RoleSelectMenu {
+ override fun createContext(
+ event: SelectMenuInteractionCreateEvent,
+ interactionResponse: PublicMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+ ): PublicRoleSelectMenuContext = PublicRoleSelectMenuContext(
+ this, event, interactionResponse, cache
+ )
+
+ override fun apply(builder: ActionRowBuilder): Unit = applyRoleSelectMenu(this, builder)
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenuContext.kt
new file mode 100644
index 0000000000..aed04315c8
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenuContext.kt
@@ -0,0 +1,21 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.role
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.types.PublicInteractionContext
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+
+/** Class representing the execution context for a public-only role select (dropdown) menu. **/
+public class PublicRoleSelectMenuContext(
+ override val component: PublicRoleSelectMenu,
+ override val event: SelectMenuInteractionCreateEvent,
+ override val interactionResponse: PublicMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+) : RoleSelectMenuContext(component, event, cache), PublicInteractionContext
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt
new file mode 100644
index 0000000000..e100271562
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt
@@ -0,0 +1,26 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.role
+
+import com.kotlindiscord.kord.extensions.components.menus.OPTIONS_MAX
+import com.kotlindiscord.kord.extensions.components.menus.SelectMenu
+import dev.kord.rest.builder.component.ActionRowBuilder
+
+/** Interface for role select menus. **/
+public interface RoleSelectMenu {
+
+ /** Apply the role select menu to an action row builder. **/
+ public fun applyRoleSelectMenu(selectMenu: SelectMenu<*, *>, builder: ActionRowBuilder) {
+ if (selectMenu.maximumChoices == null) selectMenu.maximumChoices = OPTIONS_MAX
+
+ builder.roleSelect(selectMenu.id) {
+ this.allowedValues = selectMenu.minimumChoices..selectMenu.maximumChoices!!
+ this.placeholder = selectMenu.placeholder
+ this.disabled = selectMenu.disabled
+ }
+ }
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenuContext.kt
new file mode 100644
index 0000000000..a23d651666
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenuContext.kt
@@ -0,0 +1,35 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.role
+
+import com.kotlindiscord.kord.extensions.components.menus.SelectMenu
+import com.kotlindiscord.kord.extensions.components.menus.SelectMenuContext
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import dev.kord.common.annotation.KordExperimental
+import dev.kord.common.annotation.KordUnsafe
+import dev.kord.common.entity.Snowflake
+import dev.kord.core.behavior.RoleBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+
+/** Abstract class representing the execution context of a role select (dropdown) menu component. **/
+public abstract class RoleSelectMenuContext(
+ component: SelectMenu<*, *>,
+ event: SelectMenuInteractionCreateEvent,
+ cache: MutableStringKeyedMap,
+) : SelectMenuContext(component, event, cache) {
+ /** Menu options that were selected by the user before de-focusing the menu. **/
+ @OptIn(KordUnsafe::class, KordExperimental::class)
+ public val selected: List by lazy {
+ if (event.interaction.data.guildId.value == null) {
+ error("A role select menu cannot be used outside guilds.")
+ } else {
+ event.interaction.values.map {
+ event.kord.unsafe.role(event.interaction.data.guildId.value!!, Snowflake(it))
+ }
+ }
+ }
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenu.kt
new file mode 100644
index 0000000000..012dbce302
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenu.kt
@@ -0,0 +1,38 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.string
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import com.kotlindiscord.kord.extensions.utils.scheduling.Task
+import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+import dev.kord.rest.builder.component.ActionRowBuilder
+import dev.kord.rest.builder.component.SelectOptionBuilder
+import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder
+
+public typealias InitialEphemeralSelectMenuResponseBuilder =
+ (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)?
+
+/** Class representing an ephemeral-only string select (dropdown) menu. **/
+public open class EphemeralStringSelectMenu(
+ timeoutTask: Task?,
+ public override val modal: (() -> M)? = null,
+) : EphemeralSelectMenu, M>(timeoutTask), StringSelectMenu {
+ override val options: MutableList = mutableListOf()
+
+ override fun createContext(
+ event: SelectMenuInteractionCreateEvent,
+ interactionResponse: EphemeralMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+ ): EphemeralStringSelectMenuContext = EphemeralStringSelectMenuContext(
+ this, event, interactionResponse, cache
+ )
+
+ override fun apply(builder: ActionRowBuilder): Unit = applyStringSelectMenu(this, builder)
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenuContext.kt
new file mode 100644
index 0000000000..a533bc5883
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenuContext.kt
@@ -0,0 +1,21 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.string
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+
+/** Class representing the execution context for an ephemeral-only string select (dropdown) menu. **/
+public class EphemeralStringSelectMenuContext(
+ override val component: EphemeralStringSelectMenu,
+ override val event: SelectMenuInteractionCreateEvent,
+ override val interactionResponse: EphemeralMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+) : StringSelectMenuContext(component, event, cache), EphemeralInteractionContext
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenu.kt
new file mode 100644
index 0000000000..28083c12fb
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenu.kt
@@ -0,0 +1,38 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.string
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import com.kotlindiscord.kord.extensions.utils.scheduling.Task
+import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+import dev.kord.rest.builder.component.ActionRowBuilder
+import dev.kord.rest.builder.component.SelectOptionBuilder
+import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder
+
+public typealias InitialPublicSelectMenuResponseBuilder =
+ (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)?
+
+/** Class representing a public-only string select (dropdown) menu. **/
+public open class PublicStringSelectMenu(
+ timeoutTask: Task?,
+ public override val modal: (() -> M)? = null,
+) : PublicSelectMenu, M>(timeoutTask), StringSelectMenu {
+ override val options: MutableList = mutableListOf()
+
+ override fun createContext(
+ event: SelectMenuInteractionCreateEvent,
+ interactionResponse: PublicMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+ ): PublicStringSelectMenuContext = PublicStringSelectMenuContext(
+ this, event, interactionResponse, cache
+ )
+
+ override fun apply(builder: ActionRowBuilder): Unit = applyStringSelectMenu(this, builder)
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenuContext.kt
similarity index 70%
rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt
rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenuContext.kt
index a3d3436aff..16a124d5e7 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenuContext.kt
@@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
-package com.kotlindiscord.kord.extensions.components.menus
+package com.kotlindiscord.kord.extensions.components.menus.string
import com.kotlindiscord.kord.extensions.components.forms.ModalForm
import com.kotlindiscord.kord.extensions.types.PublicInteractionContext
@@ -12,10 +12,10 @@ import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior
import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
-/** Class representing the execution context for a public-only select (dropdown) menu. **/
-public class PublicSelectMenuContext(
- override val component: PublicSelectMenu,
+/** Class representing the execution context for a public-only string select (dropdown) menu. **/
+public class PublicStringSelectMenuContext(
+ override val component: PublicStringSelectMenu,
override val event: SelectMenuInteractionCreateEvent,
override val interactionResponse: PublicMessageInteractionResponseBehavior,
- cache: MutableStringKeyedMap
-) : SelectMenuContext(component, event, cache), PublicInteractionContext
+ cache: MutableStringKeyedMap,
+) : StringSelectMenuContext(component, event, cache), PublicInteractionContext
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt
new file mode 100644
index 0000000000..d5babab887
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt
@@ -0,0 +1,85 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.string
+
+import com.kotlindiscord.kord.extensions.components.menus.*
+import dev.kord.rest.builder.component.ActionRowBuilder
+import dev.kord.rest.builder.component.SelectOptionBuilder
+import dev.kord.rest.builder.component.StringSelectBuilder
+import dev.kord.rest.builder.component.options
+
+/** Maximum length for an option's description. **/
+public const val DESCRIPTION_MAX: Int = 100
+
+/** Maximum length for an option's label. **/
+public const val LABEL_MAX: Int = 100
+
+/** Maximum length for an option's value. **/
+public const val VALUE_MAX: Int = 100
+
+/** Interface for string select menus. **/
+public interface StringSelectMenu {
+ /** List of options for the user to choose from. **/
+ public val options: MutableList
+
+ /** Add an option to this select menu. **/
+ @Suppress("UnnecessaryParentheses")
+ public suspend fun option(
+ label: String,
+ value: String,
+
+ // TODO: Check this is fixed in later versions of the compiler
+ // This is nullable like this due to a compiler bug: https://youtrack.jetbrains.com/issue/KT-51820
+ body: (suspend SelectOptionBuilder.() -> Unit)? = null,
+ ) {
+ val builder = SelectOptionBuilder(label, value)
+
+ if (body != null) {
+ body(builder)
+ }
+
+ if ((builder.description?.length ?: 0) > DESCRIPTION_MAX) {
+ error("Option descriptions must not be longer than $DESCRIPTION_MAX characters.")
+ }
+
+ if (builder.label.length > LABEL_MAX) {
+ error("Option labels must not be longer than $LABEL_MAX characters.")
+ }
+
+ if (builder.value.length > VALUE_MAX) {
+ error("Option values must not be longer than $VALUE_MAX characters.")
+ }
+
+ options.add(builder)
+ }
+
+ /** Apply the string select menu to an action row builder. **/
+ public fun applyStringSelectMenu(selectMenu: SelectMenu<*, *>, builder: ActionRowBuilder) {
+ if (selectMenu.maximumChoices == null || selectMenu.maximumChoices!! > options.size) {
+ selectMenu.maximumChoices = options.size
+ }
+
+ builder.stringSelect(selectMenu.id) {
+ this.allowedValues = selectMenu.minimumChoices..selectMenu.maximumChoices!!
+ ((this as? StringSelectBuilder)?.options ?: mutableListOf()).addAll(this@StringSelectMenu.options)
+ this.placeholder = selectMenu.placeholder
+ this.disabled = selectMenu.disabled
+ }
+ }
+
+ /** Validate the options of the string select menu. **/
+ @Suppress("UnnecessaryParentheses")
+ public fun validateOptions() {
+ if (this.options.isEmpty()) {
+ error("Menu components must have at least one option.")
+ }
+
+ if (this.options.size > OPTIONS_MAX) {
+ error("Menu components must not have more than $OPTIONS_MAX options.")
+ }
+ }
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenuContext.kt
new file mode 100644
index 0000000000..77c5b24b87
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenuContext.kt
@@ -0,0 +1,22 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.string
+
+import com.kotlindiscord.kord.extensions.components.menus.SelectMenu
+import com.kotlindiscord.kord.extensions.components.menus.SelectMenuContext
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+
+/** Abstract class representing the execution context of a string select (dropdown) menu component. **/
+public abstract class StringSelectMenuContext(
+ component: SelectMenu<*, *>,
+ event: SelectMenuInteractionCreateEvent,
+ cache: MutableStringKeyedMap,
+) : SelectMenuContext(component, event, cache) {
+ /** Menu options that were selected by the user before de-focusing the menu. **/
+ public val selected: List by lazy { event.interaction.values }
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenu.kt
new file mode 100644
index 0000000000..b3a7384d69
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenu.kt
@@ -0,0 +1,35 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.user
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import com.kotlindiscord.kord.extensions.utils.scheduling.Task
+import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+import dev.kord.rest.builder.component.ActionRowBuilder
+import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder
+
+public typealias InitialEphemeralSelectMenuResponseBuilder =
+ (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)?
+
+/** Class representing an ephemeral-only user select (dropdown) menu. **/
+public open class EphemeralUserSelectMenu(
+ timeoutTask: Task?,
+ public override val modal: (() -> M)? = null,
+) : EphemeralSelectMenu, M>(timeoutTask), UserSelectMenu {
+ override fun createContext(
+ event: SelectMenuInteractionCreateEvent,
+ interactionResponse: EphemeralMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+ ): EphemeralUserSelectMenuContext = EphemeralUserSelectMenuContext(
+ this, event, interactionResponse, cache
+ )
+
+ override fun apply(builder: ActionRowBuilder): Unit = applyUserSelectMenu(this, builder)
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenuContext.kt
new file mode 100644
index 0000000000..eeaaca34ac
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenuContext.kt
@@ -0,0 +1,21 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.user
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+
+/** Class representing the execution context for an ephemeral-only user select (dropdown) menu. **/
+public class EphemeralUserSelectMenuContext(
+ override val component: EphemeralUserSelectMenu,
+ override val event: SelectMenuInteractionCreateEvent,
+ override val interactionResponse: EphemeralMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+) : UserSelectMenuContext(component, event, cache), EphemeralInteractionContext
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenu.kt
new file mode 100644
index 0000000000..dbdb77699b
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenu.kt
@@ -0,0 +1,35 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.user
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import com.kotlindiscord.kord.extensions.utils.scheduling.Task
+import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+import dev.kord.rest.builder.component.ActionRowBuilder
+import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder
+
+public typealias InitialPublicSelectMenuResponseBuilder =
+ (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)?
+
+/** Class representing a public-only user select (dropdown) menu. **/
+public open class PublicUserSelectMenu(
+ timeoutTask: Task?,
+ public override val modal: (() -> M)? = null,
+) : PublicSelectMenu, M>(timeoutTask), UserSelectMenu {
+ override fun createContext(
+ event: SelectMenuInteractionCreateEvent,
+ interactionResponse: PublicMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+ ): PublicUserSelectMenuContext = PublicUserSelectMenuContext(
+ this, event, interactionResponse, cache
+ )
+
+ override fun apply(builder: ActionRowBuilder): Unit = applyUserSelectMenu(this, builder)
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenuContext.kt
new file mode 100644
index 0000000000..7f0bbab6ad
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenuContext.kt
@@ -0,0 +1,21 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.user
+
+import com.kotlindiscord.kord.extensions.components.forms.ModalForm
+import com.kotlindiscord.kord.extensions.types.PublicInteractionContext
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+
+/** Class representing the execution context for a public-only user select (dropdown) menu. **/
+public class PublicUserSelectMenuContext(
+ override val component: PublicUserSelectMenu,
+ override val event: SelectMenuInteractionCreateEvent,
+ override val interactionResponse: PublicMessageInteractionResponseBehavior,
+ cache: MutableStringKeyedMap,
+) : UserSelectMenuContext(component, event, cache), PublicInteractionContext
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt
new file mode 100644
index 0000000000..42333c981b
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt
@@ -0,0 +1,26 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.user
+
+import com.kotlindiscord.kord.extensions.components.menus.OPTIONS_MAX
+import com.kotlindiscord.kord.extensions.components.menus.SelectMenu
+import dev.kord.rest.builder.component.ActionRowBuilder
+
+/** Interface for user select menus. **/
+public interface UserSelectMenu {
+
+ /** Apply the user select menu to an action row builder. **/
+ public fun applyUserSelectMenu(selectMenu: SelectMenu<*, *>, builder: ActionRowBuilder) {
+ if (selectMenu.maximumChoices == null) selectMenu.maximumChoices = OPTIONS_MAX
+
+ builder.userSelect(selectMenu.id) {
+ this.allowedValues = selectMenu.minimumChoices..selectMenu.maximumChoices!!
+ this.placeholder = selectMenu.placeholder
+ this.disabled = selectMenu.disabled
+ }
+ }
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenuContext.kt
new file mode 100644
index 0000000000..c8ed620294
--- /dev/null
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenuContext.kt
@@ -0,0 +1,29 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package com.kotlindiscord.kord.extensions.components.menus.user
+
+import com.kotlindiscord.kord.extensions.components.menus.SelectMenu
+import com.kotlindiscord.kord.extensions.components.menus.SelectMenuContext
+import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap
+import dev.kord.common.annotation.KordExperimental
+import dev.kord.common.annotation.KordUnsafe
+import dev.kord.common.entity.Snowflake
+import dev.kord.core.behavior.UserBehavior
+import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent
+
+/** Abstract class representing the execution context of a user select (dropdown) menu component. **/
+public abstract class UserSelectMenuContext(
+ component: SelectMenu<*, *>,
+ event: SelectMenuInteractionCreateEvent,
+ cache: MutableStringKeyedMap,
+) : SelectMenuContext(component, event, cache) {
+ /** Menu options that were selected by the user before de-focusing the menu. **/
+ @OptIn(KordUnsafe::class, KordExperimental::class)
+ public val selected: List by lazy {
+ event.interaction.values.map { event.kord.unsafe.user(Snowflake(it)) }
+ }
+}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt
index 0c72487f35..bc6bb6fd94 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt
@@ -24,8 +24,8 @@ import dev.kord.core.Kord
import dev.kord.core.entity.channel.DmChannel
import dev.kord.core.entity.channel.GuildMessageChannel
import dev.kord.core.event.Event
+import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.Job
-import mu.KotlinLogging
import org.koin.core.component.inject
import java.util.*
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/Extension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/Extension.kt
index fe944ca58e..106e98f919 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/Extension.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/Extension.kt
@@ -25,7 +25,7 @@ import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
import dev.kord.core.Kord
import dev.kord.core.event.Event
import dev.kord.gateway.Intent
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
private val logger = KotlinLogging.logger {}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Commands.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Commands.kt
index 275924ddbe..d978fa8a42 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Commands.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Commands.kt
@@ -26,7 +26,7 @@ import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand
import com.kotlindiscord.kord.extensions.commands.chat.ChatGroupCommand
import com.kotlindiscord.kord.extensions.components.forms.ModalForm
import dev.kord.gateway.Intent
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger {}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt
index a9385d9f1b..afdc8f22c6 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt
@@ -12,7 +12,7 @@ import com.kotlindiscord.kord.extensions.events.EventHandler
import dev.kord.core.enableEvent
import dev.kord.core.event.Event
import dev.kord.gateway.Intents
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
/**
* DSL function for easily registering an event handler.
@@ -44,10 +44,10 @@ public suspend inline fun Extension.event(
logger.error(e) { "Failed to register event handler - $e" }
}
- val fakeBuilder = Intents.IntentsBuilder()
+ val fakeBuilder = Intents.Builder()
fakeBuilder.enableEvent()
- intents += fakeBuilder.flags().values
+ intents += fakeBuilder.build().values
return eventHandler
}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt
index 2d9ea6395c..5e24f24965 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt
@@ -22,7 +22,7 @@ import com.kotlindiscord.kord.extensions.utils.deleteIgnoringNotFound
import com.kotlindiscord.kord.extensions.utils.getLocale
import com.kotlindiscord.kord.extensions.utils.translate
import dev.kord.core.event.message.MessageCreateEvent
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
private val logger = KotlinLogging.logger {}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt
index 77888a674f..ac69c72e73 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt
@@ -111,7 +111,10 @@ public class SentryExtension : Extension() {
/** Arguments for the feedback command. **/
public class FeedbackMessageArgs : Arguments() {
/** Sentry event ID. **/
- public val id: SentryId by sentryId("id", "extensions.sentry.arguments.id")
+ public val id: SentryId by sentryId {
+ name = "id"
+ description = "extensions.sentry.arguments.id"
+ }
/** Feedback message to submit to Sentry. **/
public val feedback: String by coalescingString {
@@ -122,15 +125,16 @@ public class SentryExtension : Extension() {
/** Arguments for the feedback command. **/
public class FeedbackSlashArgs : Arguments() {
- // TODO: It's impossible to translate these right now
-
/** Sentry event ID. **/
- public val id: SentryId by sentryId("id", "Sentry event ID")
+ public val id: SentryId by sentryId {
+ name = "id"
+ description = "extensions.sentry.arguments.id"
+ }
- /** Feedback message to submit to Sentry. **/
+ /** Feedback message to submit to Sentry. **/
public val feedback: String by string {
name = "feedback"
- description = "Feedback to send to the developers"
+ description = "extensions.sentry.arguments.feedback"
}
}
}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/ResourceBundleTranslations.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/ResourceBundleTranslations.kt
index ad9af50fa3..de738af64d 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/ResourceBundleTranslations.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/ResourceBundleTranslations.kt
@@ -9,8 +9,8 @@ package com.kotlindiscord.kord.extensions.i18n
import com.ibm.icu.text.MessageFormat
import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder
import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
-import mu.KLogger
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
import java.util.*
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt
index 8b1e94907a..26852dfad9 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt
@@ -30,6 +30,9 @@ public object SupportedLocales {
/** German locale. **/
public val GERMAN: Locale = Locale("de", "de")
+ /** Korean locale. **/
+ public val KOREAN: Locale = Locale("ko")
+
/** Polish locale. **/
public val POLISH: Locale = Locale("pl", "pl")
@@ -89,6 +92,11 @@ public object SupportedLocales {
"deutsch" to GERMAN,
"german" to GERMAN,
+ "ko" to KOREAN,
+ "ko_ko" to KOREAN,
+ "korean" to KOREAN,
+ "한국어" to KOREAN,
+
"portugues" to PORTUGUESE,
"portuguese" to PORTUGUESE,
"português" to PORTUGUESE,
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/koin/KordExContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/koin/KordExContext.kt
index b624b68b1e..ab93a2d0e4 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/koin/KordExContext.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/koin/KordExContext.kt
@@ -23,111 +23,103 @@ import org.koin.dsl.KoinAppDeclaration
* @see org.koin.core.context.GlobalContext
*/
public object KordExContext : KoinContext {
- /** The current [Koin] instance. */
- private var koin: Koin? = null
-
- /** The current [KoinApplication]. */
- private var koinApp: KoinApplication? = null
-
- /**
- * Gets the [Koin] instance.
- *
- * @throws IllegalStateException [KoinApplication] has not yet been started.
- */
- override fun get(): Koin = koin ?: error("KordEx KoinApplication has not been started")
-
- /** Gets the [Koin] instance or null if the [KoinApplication] has not yet been started. */
- override fun getOrNull(): Koin? = koin
-
- /** Gets the [KoinApplication] or null if the [KoinApplication] has not yet been started. */
- public fun getKoinApplicationOrNull(): KoinApplication? = koinApp
-
- /**
- * Registers a [KoinApplication] to as the current one for this context.
- *
- * @param koinApplication The application to registers.
- *
- * @throws KoinAppAlreadyStartedException The [KoinApplication] has already been instantiated.
- */
- private fun register(koinApplication: KoinApplication) {
- if (koin != null) {
- throw KoinAppAlreadyStartedException("KordEx Koin Application has already been started")
- }
-
- koinApp = koinApplication
- koin = koinApplication.koin
- }
-
- /** Closes and removes the current [Koin] instance. */
- override fun stopKoin(): Unit = synchronized(this) {
- koin?.close()
- koin = null
- }
-
- /**
- * Starts using the provided [KoinApplication] as the current one for this context.
- *
- * @param koinApplication The application to start with.
- *
- * @throws KoinAppAlreadyStartedException The [KoinApplication] has already been instantiated.
- */
- override fun startKoin(koinApplication: KoinApplication): KoinApplication = synchronized(this) {
- register(koinApplication)
- koinApplication.createEagerInstances()
-
- return koinApplication
- }
-
- /**
- * Starts using the provided [KoinAppDeclaration] to create the [KoinApplication] for this context.
- *
- * @param appDeclaration The application declaration to start with.
- *
- * @throws KoinAppAlreadyStartedException The [KoinApplication] has already been instantiated.
- */
- override fun startKoin(appDeclaration: KoinAppDeclaration): KoinApplication = synchronized(this) {
- val koinApplication = KoinApplication.init()
-
- register(koinApplication)
- appDeclaration(koinApplication)
- koinApplication.createEagerInstances()
-
- return koinApplication
- }
-
- /**
- * Loads a module into the [Koin] instance.
- *
- * @param module The module to load.
- */
- override fun loadKoinModules(module: Module): Unit = synchronized(this) {
- get().loadModules(listOf(module))
- }
-
- /**
- * Loads modules into the [Koin] instance.
- *
- * @param modules The modules to load.
- */
- override fun loadKoinModules(modules: List): Unit = synchronized(this) {
- get().loadModules(modules)
- }
-
- /**
- * Unloads a module from the [Koin] instance.
- *
- * @param module The module to unload.
- */
- override fun unloadKoinModules(module: Module): Unit = synchronized(this) {
- get().unloadModules(listOf(module))
- }
-
- /**
- * Unloads modules from the [Koin] instance.
- *
- * @param modules The modules to unload.
- */
- override fun unloadKoinModules(modules: List): Unit = synchronized(this) {
- get().unloadModules(modules)
- }
+ /** The current [Koin] instance. */
+ private var koin: Koin? = null
+
+ /** The current [KoinApplication]. */
+ private var koinApp: KoinApplication? = null
+
+ /**
+ * Gets the [Koin] instance.
+ *
+ * @throws IllegalStateException [KoinApplication] has not yet been started.
+ */
+ override fun get(): Koin = koin ?: error("KordEx KoinApplication has not been started")
+
+ /** Gets the [Koin] instance or null if the [KoinApplication] has not yet been started. */
+ override fun getOrNull(): Koin? = koin
+
+ /** Gets the [KoinApplication] or null if the [KoinApplication] has not yet been started. */
+ public fun getKoinApplicationOrNull(): KoinApplication? = koinApp
+
+ /**
+ * Registers a [KoinApplication] to as the current one for this context.
+ *
+ * @param koinApplication The application to registers.
+ *
+ * @throws KoinAppAlreadyStartedException The [KoinApplication] has already been instantiated.
+ */
+ private fun register(koinApplication: KoinApplication) {
+ if (koin != null) {
+ throw KoinAppAlreadyStartedException("KordEx Koin Application has already been started")
+ }
+
+ koinApp = koinApplication
+ koin = koinApplication.koin
+ }
+
+ /** Closes and removes the current [Koin] instance. */
+ override fun stopKoin(): Unit = synchronized(this) {
+ koin?.close()
+ koin = null
+ }
+
+ /**
+ * Starts using the provided [KoinApplication] as the current one for this context.
+ *
+ * @param koinApplication The application to start with.
+ *
+ * @throws KoinAppAlreadyStartedException The [KoinApplication] has already been instantiated.
+ */
+ override fun startKoin(koinApplication: KoinApplication): KoinApplication = synchronized(this) {
+ register(koinApplication)
+ koinApplication.createEagerInstances()
+
+ return koinApplication
+ }
+
+ /**
+ * Starts using the provided [KoinAppDeclaration] to create the [KoinApplication] for this context.
+ *
+ * @param appDeclaration The application declaration to start with.
+ *
+ * @throws KoinAppAlreadyStartedException The [KoinApplication] has already been instantiated.
+ */
+ override fun startKoin(appDeclaration: KoinAppDeclaration): KoinApplication = synchronized(this) {
+ val koinApplication = KoinApplication.init()
+
+ register(koinApplication)
+ appDeclaration(koinApplication)
+ koinApplication.createEagerInstances()
+
+ return koinApplication
+ }
+
+ /** Mirroring the global Koin instance implementation. **/
+ override fun loadKoinModules(module: Module, createEagerInstances: Boolean): Unit = synchronized(this) {
+ get().loadModules(listOf(module), createEagerInstances = createEagerInstances)
+ }
+
+ /** Mirroring the global Koin instance implementation. **/
+ override fun loadKoinModules(modules: List, createEagerInstances: Boolean): Unit = synchronized(this) {
+ get().loadModules(modules, createEagerInstances = createEagerInstances)
+ }
+
+ /**
+ * Unloads a module from the [Koin] instance.
+ *
+ * @param module The module to unload.
+ */
+ override fun unloadKoinModules(module: Module): Unit = synchronized(this) {
+ get().unloadModules(listOf(module))
+ }
+
+ /**
+ * Unloads modules from the [Koin] instance.
+ *
+ * @param modules The modules to unload.
+ */
+ override fun unloadKoinModules(modules: List): Unit = synchronized(this) {
+ get().unloadModules(modules)
+ }
}
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BasePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BasePaginator.kt
index 4a5a30f4fb..8d0647bcf2 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BasePaginator.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BasePaginator.kt
@@ -15,7 +15,7 @@ import dev.kord.core.Kord
import dev.kord.core.behavior.UserBehavior
import dev.kord.core.entity.ReactionEmoji
import dev.kord.rest.builder.message.EmbedBuilder
-import mu.KotlinLogging
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject
import java.util.*
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Converters.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Converters.kt
deleted file mode 100644
index 117e10ef01..0000000000
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Converters.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-@file:OptIn(
- ConverterToMulti::class,
- ConverterToOptional::class
-)
-
-package com.kotlindiscord.kord.extensions.sentry
-
-import com.kotlindiscord.kord.extensions.commands.Arguments
-import com.kotlindiscord.kord.extensions.commands.converters.*
-import io.sentry.protocol.SentryId
-
-// TODO: Move to annotation
-
-/**
- * Create a Sentry ID argument converter, for single arguments.
- *
- * @see SentryIdConverter
- */
-public fun Arguments.sentryId(displayName: String, description: String): SingleConverter =
- arg(displayName, description, SentryIdConverter())
-
-/**
- * Create an optional Sentry ID argument converter, for single arguments.
- *
- * @see SentryIdConverter
- */
-public fun Arguments.optionalSentryId(displayName: String, description: String): OptionalConverter =
- arg(displayName, description, SentryIdConverter().toOptional())
-
-/**
- * Create a Sentry ID argument converter, for lists of arguments.
- *
- * @see SentryIdConverter
- */
-public fun Arguments.sentryIdList(
- displayName: String,
- description: String,
- required: Boolean = true
-): ListConverter =
- arg(displayName, description, SentryIdConverter().toList(required))
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryContext.kt
index 71f3c39056..3d8fd39f09 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryContext.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryContext.kt
@@ -9,10 +9,10 @@
package com.kotlindiscord.kord.extensions.sentry
import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
import io.sentry.*
import io.sentry.protocol.SentryId
-import mu.KLogger
-import mu.KotlinLogging
import org.koin.core.component.inject
/**
diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryIdConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryIdConverter.kt
index 1327d2da54..fb31cb553f 100644
--- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryIdConverter.kt
+++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryIdConverter.kt
@@ -10,6 +10,10 @@ import com.kotlindiscord.kord.extensions.DiscordRelayedException
import com.kotlindiscord.kord.extensions.commands.Argument
import com.kotlindiscord.kord.extensions.commands.CommandContext
import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter
+import com.kotlindiscord.kord.extensions.commands.converters.Validator
+import com.kotlindiscord.kord.extensions.i18n.DEFAULT_KORDEX_BUNDLE
+import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter
+import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType
import com.kotlindiscord.kord.extensions.parser.StringParser
import dev.kord.core.entity.interaction.OptionValue
import dev.kord.core.entity.interaction.StringOptionValue
@@ -23,8 +27,15 @@ import io.sentry.protocol.SentryId
* @see sentryId
* @see sentryIdList
*/
-public class SentryIdConverter : SingleConverter