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() { +@Converter( + "sentryId", + types = [ConverterType.SINGLE, ConverterType.LIST, ConverterType.OPTIONAL] +) +public class SentryIdConverter( + override var validator: Validator = null +) : SingleConverter() { override val signatureTypeString: String = "extensions.sentry.converter.sentryId.signatureType" + override val bundle: String = DEFAULT_KORDEX_BUNDLE override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean { val arg: String = named ?: parser?.parseNext()?.data ?: return false diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Channel.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Channel.kt index 5fed0ae398..e2830e0f9f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Channel.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Channel.kt @@ -18,8 +18,8 @@ import dev.kord.core.entity.channel.TopGuildChannel import dev.kord.core.entity.channel.TopGuildMessageChannel import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.rest.Image +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.flow.firstOrNull -import mu.KotlinLogging private val logger = KotlinLogging.logger {} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_ChannelType.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_ChannelType.kt index b55f681fef..3b1e51bc5e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_ChannelType.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_ChannelType.kt @@ -25,6 +25,7 @@ public fun ChannelType.toTranslationKey(): String = when (this) { ChannelType.PrivateThread -> "channelType.privateThread" ChannelType.GuildDirectory -> "channelType.guildDirectory" ChannelType.GuildForum -> "channelType.guildForum" + ChannelType.GuildMedia -> "channelType.guildMedia" is ChannelType.Unknown -> "channelType.unknown" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Environment.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Environment.kt index adbeaa881f..2d1c2b035c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Environment.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Environment.kt @@ -6,7 +6,7 @@ package com.kotlindiscord.kord.extensions.utils -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import kotlin.io.path.Path import kotlin.io.path.isRegularFile import kotlin.io.path.readLines diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt index dde957c0ed..a040f307f1 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt @@ -25,9 +25,9 @@ import dev.kord.core.event.message.* import dev.kord.rest.builder.message.create.MessageCreateBuilder import dev.kord.rest.builder.message.create.allowedMentions import dev.kord.rest.request.RestRequestException +import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.http.* import kotlinx.coroutines.* -import mu.KotlinLogging private val logger = KotlinLogging.logger {} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/scheduling/Scheduler.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/scheduling/Scheduler.kt index 8376d4c118..fabb85f9a6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/scheduling/Scheduler.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/scheduling/Scheduler.kt @@ -8,11 +8,11 @@ package com.kotlindiscord.kord.extensions.utils.scheduling +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel -import mu.KotlinLogging import java.util.* import kotlin.coroutines.CoroutineContext import kotlin.time.Duration diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/scheduling/Task.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/scheduling/Task.kt index ff6d51346a..6f7e1018d3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/scheduling/Task.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/scheduling/Task.kt @@ -15,12 +15,12 @@ import com.kotlindiscord.kord.extensions.sentry.SentryContext import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap import dev.kord.core.Kord +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.* import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime -import mu.KLogger -import mu.KotlinLogging import org.koin.core.component.inject import kotlin.time.Duration import kotlin.time.ExperimentalTime diff --git a/kord-extensions/src/main/resources/translations/kordex/strings.properties b/kord-extensions/src/main/resources/translations/kordex/strings.properties index 77754712c8..0060a41648 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings.properties @@ -16,6 +16,7 @@ channelType.groupDm=Group DM channelType.guildCategory=Category channelType.guildDirectory=Guild Directory channelType.guildForum=Forum +channelType.guildMedia=Media channelType.guildNews=Announcement channelType.guildStageVoice=Stage channelType.guildText=Text diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_de_DE.properties b/kord-extensions/src/main/resources/translations/kordex/strings_de_DE.properties index d74f5338aa..162011971c 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_de_DE.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_de_DE.properties @@ -218,7 +218,7 @@ checks.guildNsfwLevelLower.failed= permission.useEmbeddedActivities= permission.unknown= permission.useApplicationCommands= -channelType.guildForum= +channelType.guildForum=Forum converters.string.error.invalid.tooLong= converters.string.error.invalid.tooShort= nsfwLevel.ageRestricted= diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties index c051c573ec..df70bdb7e0 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties @@ -16,6 +16,7 @@ channelType.groupDm=Group DM channelType.guildCategory=Category channelType.guildDirectory=Guild Directory channelType.guildForum=Forum +channelType.guildMedia=Media channelType.guildNews=Announcement channelType.guildStageVoice=Stage channelType.guildText=Text @@ -229,4 +230,8 @@ converters.timestamp.error.invalid=Value `{0}` is not a valid timestamp. converters.channel.error.wrongType=Supplied channel is the wrong type (**{0}**) - must be one of: {1} converters.string.error.invalid.tooShort=Value `{0}` must not be shorter than `{1}` characters. converters.string.error.invalid.tooLong=Value `{0}` must not be longer than `{1}` characters. -permission.sendVoiceMessages= +permission.sendVoiceMessages=Send Voice Messages +converters.tag.signatureType=tag +converters.tag.error.wrongChannelType=Please run this command in a forum thread +converters.tag.error.wrongChannelTypeWithGetter=Please run this command in a forum thread, or provide one as an argument +converters.tag.error.unknownTag=Unknown tag: `{0}` diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_ko.properties b/kord-extensions/src/main/resources/translations/kordex/strings_ko.properties new file mode 100644 index 0000000000..c860d5afe9 --- /dev/null +++ b/kord-extensions/src/main/resources/translations/kordex/strings_ko.properties @@ -0,0 +1,232 @@ + + +argumentParser.error.notAllValid=인자 `{0}`은 {1} {1, plural, =1 {개의 값} other {개의 값들}}을 입력받았지만, {3}에 {2, plural, =0 {전부 적절하지 않았습니다} other {오직 {2}개만 적절한 값이었습니다 }}. +argumentParser.error.invalidValue=인자 `{0}`에 지원되지 않는 값 ({1} 입력 가능) +argumentParser.error.unknownConverterType=알 수 없는 변환기 형식: `{0}` +channelType.dm=다이렉트 메시지 +channelType.guildCategory=분류 +channelType.guildNews=공지 +channelType.guildVoice=보이스 +channelType.publicNewsThread=뉴스 스레드 +channelType.unknown=알 수 없음 +checks.responseTemplate=**오류:** {0} +checks.inChannel.failed=**{0}**에서여야 함 +checks.notInChannel.failed=**{0}**에서가 아니어야 함 +checks.notInCategory.failed=**{0}**에 속하지 않아야 함 +checks.channelHigher.failed=**{0}**보다 높은 채널이어야 함 +checks.channelLower.failed=**{0}**보다 낮은 채널이어야 함 +checks.channelHigherOrEqual.failed=**{0}** 또는 보다 높은 채널이어야 함 +checks.channelLowerOrEqual.failed=**{0}** 또는 보다 낮은 채널이어야 함 +checks.noGuild.failed=서버가 아니어야 함 +checks.inGuild.failed=서버 **{0}**여야 함 +checks.channelType.failed=**{0}** 형식의 채널이어야 함 +checks.notChannelType.failed=**{0}** 형식의 채널이 아니어야 함 +checks.hasPermission.failed=**{0}** 권한이 필요함 +checks.notHasPermission.failed=**{0}** 권한이 없어야 함 +checks.channelIsNsfw.failed=NSFW 채널이어야 함 +checks.notChannelIsNsfw.failed=NSFW 채널이 아니어야 함 +checks.guildNsfwLevelEqual.failed=**{0}** NSFW 레벨을 가진 서버여야 함 +checks.guildNsfwLevelNotEqual.failed=**{0}** NSFW 레벨을 가진 서버가 아니어야 함 +checks.guildNsfwLevelHigher.failed=**{0}** 보다 높은 NSFW 레벨을 가진 서버여야 함 +checks.guildNsfwLevelHigherOrEqual.failed=**{0}** 또는 보다 높은 NSFW 레벨을 가진 서버여야 함 +checks.isNotBot.failed=봇이 아니어야 함 +checks.isInThread.failed=스레드 내여야 함 +checks.topRoleEqual.failed=**{0}** 최고 역할이 있어야 함 +checks.topRoleNotEqual.failed=**{0}** 최고 역할이 없어야 함 +checks.topRoleHigher.failed=**{0}** 보다 높은 최고 역할이 있어야 함 +checks.topRoleLowerOrEqual.failed=**{0}** 또는 보다 낮은 최고 역할이 있어야 함 +commands.defaultDescription=설명 없음. +commands.error.user=안타깝게도 명령 처리 중 **오류가 발생했습니다.** 관리자에게 알려주세요. +converters.attachment.signatureType=첨부 +converters.attachment.error.slashCommandsOnly=인자 형식 `첨부`는 빗금 명령어에서만 사용 가능합니다. +converters.boolean.signatureType=예/아니오 +converters.boolean.errorType=`예` 또는 `아니오` +converters.channel.signatureType=채널 +converters.channel.error.missing=채널을 찾을 수 없음: {0} +converters.channel.error.invalid=값 `{0}` 은 유효한 채널 ID가 아닙니다. +converters.channel.error.wrongType=제공된 채널은 잘못된 형식임(**{0}**) - {1} 중 하나여야 함 +converters.color.error.unknown=색을 찾을 수 없음: `{0}` +converters.color.error.unknownOrFailed=색을 인식할 수 없음: `{0}` +converters.duration.error.signatureType=시간 +converters.duration.error.badUnitPairs=단위와 값을 같은 개수로 제공해야 합니다. 독립적인 값은 지원되지 않습니다. +converters.duration.error.invalidUnit=유효하지 않은 시간 단위: `{0}` +converters.duration.error.positiveOnly=양(+)의 시간이 필요합니다. +converters.email.signatureType=이메일 +converters.email.error.invalid=유효하지 않은 이메일 주소: `{0}` +converters.emoji.signatureType=서버 이모지 +converters.emoji.error.missing=이모지를 찾을 수 없음: `{0}` +converters.guild.error.missing=서버를 찾을 수 없음: `{0}` +converters.number.signatureType=수 +converters.number.error.invalid.defaultBase=값 `{0}`은 유효한 음이 아닌 정수가 아닙니다. +converters.number.error.invalid.tooLarge=값 `{0}`은 `{1}`보다 크지 않아야 합니다. +converters.number.error.invalid.tooSmall=값 `{0}`은 `{1}`보다 작지 않아야 합니다.. +converters.member.signatureType=멤버 +converters.member.error.invalid=값 `{0}`을 서버 멤버로 인식할 수 없거나, 잘못된 서버의 멤버입니다. +converters.message.error.invalidUrl=유효하지 않은 메시지 링크: <{0}> +converters.message.error.invalidChannelId=값 `{0}`은 유효한 채널 ID가 아닙니다. +converters.message.error.missing=메시지를 찾을 수 없음: `{0}` +converters.role.signatureType=역할 +converters.role.error.invalid=값 `{0}`은 유효한 역할 ID가 아닙니다. +converters.snowflake.error.invalid=값 `{0}`은 유효한 디스코드 ID가 아닙니다. +converters.string.signatureType=문자열 +converters.string.error.invalid.tooLong=값 `{0}`은 `{1}`문자를 넘지 않아야 합니다. +converters.supportedLocale.signatureType=언어명/코드 (locale) +converters.supportedLocale.error.unknown=알 수 없는 (또는 지원되지 않는) 언어: `{0}` +converters.tag.error.unknownTag=알 수 없는 태그: `{0}` +converters.tag.signatureType=태그 +converters.union.error.unknownConverterType=알 수 없는 컨버터 타입: `{0}` +converters.user.signatureType=사용자 +converters.user.error.missing=사용자를 찾을 수 없음: `{0}` +converters.user.error.invalid=값 `{0}`은 유효한 사용자 ID가 아닙니다. +converters.timestamp.error.invalid=값 `{0}`은 유효한 시각이 아닙니다. +argumentParser.error.errorInArgument=**인자 **`{0}`** 에 오류가 있습니다 : **{1} +argumentParser.error.requiresOneValue=인자 `{0}` 은 1개의 값만 받을 수 있지만, {1}개가 제공되었습니다. +argumentParser.error.noFilledArguments=이 명령어는 {0} {0, plural, =1 {인자가} other {인자들이}} 필요합니다 . +argumentParser.error.someFilledArguments=이 명령어는 {0} {0, plural, =1 {인자가} other {인자들이}} 필요하지만, {1}만 채워질 수 있었습니다. +channelType.groupDm=그룹 메시지 +channelType.guildDirectory=폴더 +channelType.guildForum=포럼 +channelType.guildStageVoice=스테이지 +channelType.guildText=텍스트 +checks.anyGuild.failed=서버여야 함 +checks.hasPermissions.failed=**{0}** 권한들이 필요함 +channelType.publicGuildThread=공개 스레드 +channelType.privateThread=비공개 스레드 +checks.inCategory.failed=**{0}**에 속해야 함 +checks.notInGuild.failed=서버 **{0}**이 아니어야 함 +checks.notHasPermissions.failed=**{0}** 권한들이 없어야 함 +checks.guildNsfwLevelLower.failed=**{0}** 보다 낮은 NSFW 레벨을 가진 서버여야 함 +checks.guildNsfwLevelLowerOrEqual.failed=**{0}** 또는 보다 낮은 NSFW 레벨을 가진 서버여야 함 +checks.isBot.failed=봇이어야 함 +checks.isNotInThread.failed=스레드 내가 아니어야 함 +checks.topRoleLower.failed=**{0}** 보다 낮은 최고 역할이 있어야 함 +checks.hasRole.failed=**{0}** 역할이 있어야 함 +checks.notHasRole.failed=**{0}** 역할이 없어야 함 +commands.error.missingBotPermissions=봇에게 명령 실행을 위한 권한이 없습니다!\n\n**부족한 권한:** {0} +checks.topRoleHigherOrEqual.failed=**{0}** 또는 보다 높은 최고 역할이 있어야 함 +commands.error.user.sentry.message=안타깝게도 명령 실행 중 **오류가 발생했습니다.** 오류와 관련된 정보를 제출하고 싶다면, 다음 명령어를 이용해 주세요: ```{0}feedback {1} ``` +commands.error.user.sentry.slash=안타깝게도 명령 실행 중 **오류가 발생했습니다.** 오류와 관련된 정보를 제출하고 싶다면, 다음 명령어를 이용해 주세요: ```/feedback {0} ``` +converters.color.signatureType=색 +converters.decimal.signatureType=수 +converters.decimal.error.invalid=값 `{0}`은 유효한 수가 아닙니다. +converters.duration.help=__시간을 이용하는 방법__\n\n시간은 양과 단위의 쌍으로 표시됩니다. 예를 들어, `12d`는 12일을 의미합니다.\n복합적 시간도 지원됩니다. 예를 들어, `2d 12h`는 2일 12시간을 의미합니다.\n\n다음과 같은 단위가 지원됩니다:\n\n**초:** `s`, `sec`, `second`, `seconds`\n**분:** `m`, `mi`, `min`, `minute`, `minutes`\n**시:** `h`, `hour`, `hours`\n**일:** `d`, `day`, `days`\n**주:** `w`, `week`, `weeks`\n**월:** `mo`, `month`, `months`\n**년:** `y`, `year`, `years` +converters.duration.error.negativeUnsupported=음의 값은 지원되지 않습니다. +converters.emoji.error.invalid=값 `{0}`은 유효한 이모지 ID가 아닙니다. +converters.guild.signatureType=서버 +converters.member.error.missing=멤버를 찾을 수 없음: {0} +converters.number.error.invalid.otherBase=값 `{0}`은 유효한 {1, plural, =2 {2진법} =8 {8진법} =10 {10진법} =16 {16진법} other {{1}진법} } 음이 아닌 정수가 아닙니다. +converters.message.signatureType=메시지 +converters.message.error.invalidGuildId=값 `{0}`은 유효한 서버 ID가 아닙니다. +converters.regex.signatureType.plural=정규표현식들 +converters.snowflake.signatureType=ID +converters.message.error.invalidMessageId=값 `{0}`은 유효한 메시지 ID가 아닙니다. +converters.regex.signatureType.singular=정규표현식 +converters.role.error.missing=역할을 찾을 수 없음: `{0}` +converters.string.error.invalid.tooShort=값 `{0}`은 `{1}`문자보다 길거나 같아야 합니다. +converters.tag.error.wrongChannelType=이 명령어를 포럼 스레드에서 이용해 주세요 +converters.tag.error.wrongChannelTypeWithGetter=이 명령어를 포럼 스레드에서 이용하거나, 인자로 포럼 스레드를 제공해 주세요 +converters.timestamp.signatureType=시각 +extensions.help.commandName=도움말 +extensions.help.commandDescription.aliases=**별명:** +extensions.help.commandAliases=ㄷ +extensions.help.commandArguments.command=도움말을 보고자 하는 명령어 +extensions.help.commandDescription.requiredBotPermissions=**필요한 봇 권한:** +extensions.help.commandDescription.noArguments=인자 없음. +extensions.help.paginator.title.command=명령어: {0} +extensions.help.paginator.title.commands=명령어 +extensions.help.paginator.footer={0}개 {0, plural, =1 {명령어가} other {명령어들이}} 이용 가능 +extensions.help.error.missingCommandTitle=명령어를 찾을 수 없음 +extensions.help.error.missingCommandDescription=명령어를 찾을 수 없습니다. 다음 중 하나가 원인일 수 있습니다:\n\n**»** 명령어가 존재하지 않거나, 불러와지지 않았습니다\n**»** 이 맥락에서 이용 불가합니다\n**»** 당신의 접근 권한이 없습니다\n\n오류가 있다고 생각하시면, 스태프에게 연락해 주세요. +extensions.sentry.arguments.feedback=개발자에게 보낼 피드백 +extensions.sentry.converter.sentryId.signatureType=UUID +nsfwLevel.ageRestricted="연령 제한" +nsfwLevel.default="기본 (제한 없음)" +paginator.button.delete=삭제 +paginator.button.done=완료 +paginator.button.more=더 보기 +paginator.button.group.switch=다음 뭉치 +paginator.button.less=덜 보기 +paginator.footer.page=페이지 {0}/{1} +paginator.footer.group=뭉치 {0}/{1} +extensions.sentry.commandName=피드백 +extensions.sentry.converter.error.invalid=유효하지 않은 Sentry 이벤트 ID 제공됨: `{0}` +extensions.sentry.commandAliases=피드백-Sentry +extensions.sentry.commandDescription.short=오류의 발생과 관련이 있다고 생각되는 행동에 대해 피드백해 주세요. +extensions.sentry.error.invalidId=입력받은 Sentry 이벤트 ID는 존재하지 않거나, 피드백이 필요한 상태가 아닙니다. +extensions.sentry.thanks=피드백에 대해 감사드립니다. 응답은 봇을 개선하고 오류를 수정하는 데에 이용될 것입니다! +nsfwLevel.explicit="청소년 이용불가" +nsfwLevel.safe="전체 이용가" +nsfwLevel.unknown="알 수 없는 연령등급 (`{0}`)" +permission.addReactions=반응 추가하기 +permission.administrator=관리자 +permission.all=모든 권한 +permission.attachFiles=파일 첨부 +permission.banMembers=멤버 차단하기 +permission.changeNickname=별명 변경하기 +permission.connect=연결 (음성 채널) +permission.createInstantInvite=초대 코드 만들기 +permission.createPrivateThreads=비공개 스레드 만들기 +permission.createPublicThreads=공개 스레드 만들기 +permission.deafenMembers=멤버의 헤드셋 음소거하기 +permission.embedLinks=링크 첨부 +permission.kickMembers=멤버 추방하기 +permission.manageChannels=채널 관리하기 +permission.manageExpressions=표현 관리하기 +permission.manageEvents=이벤트 관리 +permission.manageGuild=서버 관리하기 +permission.manageMessages=메시지 관리 +permission.manageNicknames=별명 관리하기 +permission.manageRoles=역할 관리하기 +permission.manageThreads=스레드 관리하기 +permission.manageWebhooks=웹후크 관리하기 +permission.mentionEveryone=모든 역할 멘션하기 +permission.moveMembers=멤버 이동 +permission.muteMembers=멤버들의 마이크 음소거하기 +permission.prioritySpeaker=우선 발언권 +permission.readMessageHistory=메시지 기록 보기 +permission.sendMessages=메시지 보내기 +permission.sendMessagesInThreads=스레드에서 메시지 보내기 +permission.sendTTSMessages=텍스트 음성 변환 메시지 전송 +permission.requestToSpeak=발언권 요청 +permission.sendVoiceMessages=음성 메시지 보내기 +permission.speak=말하기 (음성) +permission.stream=영상 +permission.timeoutMembers=타임아웃 멤버 +permission.unknown=알 수 없는 권한 (`{0}`) +permission.useApplicationCommands=애플리케이션 명령어 사용 +permission.useEmbeddedActivities=활동 사용하기 +permission.useExternalEmojis=외부 이모지 사용 +permission.useExternalStickers=외부 스티커 사용 +permission.useSoundboard=사운드보드 사용 +permission.useVAD=음성 감지 사용 +permission.viewAuditLog=감사 로그 보기 +permission.viewChannel=채널 보기 +utils.message.useThisChannel=이 명령어엔 {0} 을 이용해 주세요. +utils.colors.black=검정,검은색 +utils.colors.blurple=보라색,보라,자색,바이올렛 +utils.colors.fuchsia=핑크색,핑크,분홍색,분홍 +utils.colors.green=초록색,초록,녹색 +utils.colors.red=적색,붉은색,빨강,빨간색 +utils.colors.white=흰색,백색,하양,하얀색 +utils.colors.yellow=황색,노랑,노란색 +utils.durations.ignoredWords=하고도 +utils.units.year=년,연,년도,연도 +utils.units.month=달,월,개월 +utils.units.week=주,주일 +utils.units.day=일 +permission.viewCreatorMonetizationAnalytics=크리에이터 수익 분석 보기 +utils.string.false=0,ㄴ,아니,아니요,거짓 +utils.string.true=1,네,예,참 +extensions.help.commandDescription=도움말 보기.\n\n특정 명령어에 대한 도움말을 보려면 명령어를 지정하세요. 하위 명령어도 실제 사용과 동일한 방식으로 지정될 수 있습니다. +extensions.help.commandDescription.subCommands=**하위 명령어:** +extensions.help.commandDescription.error.argumentList=오류로 인해 인자 목록을 가져오지 못했습니다. +extensions.help.paginator.noCommands=명령어 없음. +extensions.help.paginator.title.arguments=명령어 인자 +extensions.sentry.arguments.id=Sentry 이벤트 ID +utils.message.commandNotAvailableInDm=이 명령어는 DM에서 이용할 수 없습니다. +permission.useExternalSounds=외부 사운드 사용 +utils.units.minute=분 +utils.units.hour=시간,시 +utils.units.second=초 +extensions.sentry.commandDescription.long=봇에 의해 Sentry ID를 제공받으면, 무엇을 하다가 오류가 났는지 피드백을 제출할 수 있습니다.\n\n피드백은, 무엇을 하다가 오류가 났는지와 어떤 결과를 기대했는지를 포함하는 것이 좋습니다. 하지만 다양하게 작성될 수 있습니다.\n\n**알림:** 피드백은 필수가 아닙니다. 원치 않는다면, 부담을 가질 필요가 없습니다. Sentry ID를 제공받는 시점에서, 오류 자체는 이미 제출되었습니다! +permission.viewGuildInsights=서버 요약 보기 diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_pt.properties b/kord-extensions/src/main/resources/translations/kordex/strings_pt.properties new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/kord-extensions/src/main/resources/translations/kordex/strings_pt.properties @@ -0,0 +1 @@ + diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_pt_PT.properties b/kord-extensions/src/main/resources/translations/kordex/strings_pt_PT.properties index c3fec5294b..d14fdd35d8 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_pt_PT.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_pt_PT.properties @@ -196,36 +196,36 @@ utils.string.true=1, s, sim, t, verdadeiro converters.timestamp.signatureType=Data e hora converters.timestamp.error.invalid=`{0}` não é uma data válida. permission.manageEvents=Gerenciar eventos -channelType.guildDirectory= +channelType.guildDirectory=Diretório da guilda converters.number.error.invalid.tooSmall= nsfwLevel.ageRestricted= nsfwLevel.safe= -checks.guildNsfwLevelHigher.failed= -checks.guildNsfwLevelLowerOrEqual.failed= +checks.guildNsfwLevelHigher.failed=É necessário estar num servidor com um nível NSFW acima de: **{0}** +checks.guildNsfwLevelLowerOrEqual.failed=É necessário estar num servidor com um nível NSFW de **{0}**, ou abaixo nsfwLevel.explicit= -checks.guildNsfwLevelNotEqual.failed= -checks.guildNsfwLevelHigherOrEqual.failed= +checks.guildNsfwLevelNotEqual.failed=É necessário não estar num servidor com nível NSFW: **{0}** +checks.guildNsfwLevelHigherOrEqual.failed=É necessário estar num servidor com um nível NSFW de **{0}**, ou acima converters.channel.error.wrongType= permission.manageExpressions= permission.useEmbeddedActivities= permission.timeoutMembers= permission.useApplicationCommands= -checks.guildNsfwLevelLower.failed= +checks.guildNsfwLevelLower.failed=É necessário estar num servidor com um nível NSFW abaixo de: **{0}** nsfwLevel.default= nsfwLevel.unknown= -checks.channelIsNsfw.failed= +checks.channelIsNsfw.failed=É necessário estar num canal NSFW permission.unknown= permission.useExternalStickers= -checks.guildNsfwLevelEqual.failed= +checks.guildNsfwLevelEqual.failed=É necessário estar num servidor com nível NSFW: **{0}** converters.number.error.invalid.tooLarge= converters.string.error.invalid.tooLong= converters.attachment.error.slashCommandsOnly= -checks.notHasPermissions.failed= -converters.attachment.signatureType= +checks.notHasPermissions.failed=É necessário não ter as permissões: **{0}** +converters.attachment.signatureType=ficheiro converters.string.error.invalid.tooShort= -channelType.guildForum= -checks.hasPermissions.failed= -checks.notChannelIsNsfw.failed= +channelType.guildForum=Fórum +checks.hasPermissions.failed=É necessário ter as permissões: **{0}** +checks.notChannelIsNsfw.failed=É necessário não estar num canal NSFW permission.viewCreatorMonetizationAnalytics= permission.sendVoiceMessages= permission.useSoundboard= diff --git a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/ChronoContainer.kt b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/ChronoContainer.kt index edb1e0f626..10b6f2233c 100644 --- a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/ChronoContainer.kt +++ b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/ChronoContainer.kt @@ -7,8 +7,8 @@ package com.kotlindiscord.kord.extensions.modules.time.java import com.kotlindiscord.kord.extensions.parsers.InvalidTimeUnitException +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.serialization.Serializable -import mu.KotlinLogging import java.time.* import java.time.temporal.ChronoUnit import java.time.temporal.Temporal diff --git a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt index ffc7fc96ee..dea6f20b72 100644 --- a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt +++ b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt @@ -22,8 +22,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 mu.KLogger -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging import java.time.Duration import java.time.LocalDateTime diff --git a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt index 5d8c0cca60..c8b0eaa8a9 100644 --- a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt +++ b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt @@ -22,8 +22,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 mu.KLogger -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging import net.time4j.Duration import net.time4j.IsoUnit diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_Commands.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_Commands.kt index 10ef916860..8e404c6c07 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_Commands.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_Commands.kt @@ -18,7 +18,7 @@ import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI import com.kotlindiscord.kord.extensions.modules.unsafe.commands.UnsafeMessageCommand import com.kotlindiscord.kord.extensions.modules.unsafe.commands.UnsafeSlashCommand import com.kotlindiscord.kord.extensions.modules.unsafe.commands.UnsafeUserCommand -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging private val logger = KotlinLogging.logger {} diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_SlashCommands.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_SlashCommands.kt index ffce81c770..bce7aee4ea 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_SlashCommands.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_SlashCommands.kt @@ -122,6 +122,8 @@ public suspend fun SlashGroup.unsafeSubCommand( public fun SlashGroup.unsafeSubCommand( commandObj: UnsafeSlashCommand ): UnsafeSlashCommand { + commandObj.guildId = null + if (subCommands.size >= SUBCOMMAND_AND_GROUP_LIMIT) { throw InvalidCommandException( commandObj.name, diff --git a/settings.gradle.kts b/settings.gradle.kts index baa386e7b1..e07745fc35 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,16 +1,10 @@ rootProject.name = "kord-extensions" -dependencyResolutionManagement { - versionCatalogs { - create("libs") { - from(files("libs.versions.toml")) - } - } -} - include("annotations") include("annotation-processor") +include("data-adapters:adapter-mongodb") + include("kord-extensions") include("extra-modules:extra-mappings") diff --git a/test-bot/build.gradle.kts b/test-bot/build.gradle.kts index 946e5028a0..468286e1d8 100644 --- a/test-bot/build.gradle.kts +++ b/test-bot/build.gradle.kts @@ -86,5 +86,5 @@ application { } detekt { - config = files("$projectDir/detekt.yml") + config.from(files("$projectDir/detekt.yml")) } diff --git a/test-bot/detekt.yml b/test-bot/detekt.yml index 1408159cf1..5a9e871aca 100644 --- a/test-bot/detekt.yml +++ b/test-bot/detekt.yml @@ -571,7 +571,7 @@ style: NewLineAtEndOfFile: active: true NoTabs: - active: true + active: false OptionalAbstractKeyword: active: true OptionalUnit: diff --git a/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/TestBot.kt b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/TestBot.kt index 1aec888394..2b38823221 100644 --- a/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/TestBot.kt +++ b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/TestBot.kt @@ -18,6 +18,7 @@ import com.kotlindiscord.kord.extensions.utils.env import com.kotlindiscord.kord.extensions.utils.envOrNull import dev.kord.common.Locale import dev.kord.common.entity.Snowflake +import dev.kord.gateway.ALL import dev.kord.gateway.Intents import dev.kord.gateway.PrivilegedIntent import org.koin.core.logger.Level @@ -42,7 +43,7 @@ public suspend fun main() { } intents { - +Intents.all + +Intents.ALL } i18n { @@ -82,6 +83,7 @@ public suspend fun main() { add(::ModalTestExtension) add(::PaginatorTestExtension) add(::PKTestExtension) + add(::SelectorTestExtension) add(::NestingTestExtension) } diff --git a/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt new file mode 100644 index 0000000000..c69e1d0db6 --- /dev/null +++ b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt @@ -0,0 +1,152 @@ +/* + * 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.testbot.extensions + +import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand +import com.kotlindiscord.kord.extensions.commands.application.slash.publicSubCommand +import com.kotlindiscord.kord.extensions.components.* +import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand +import com.kotlindiscord.kord.extensions.types.respond +import dev.kord.common.entity.ChannelType +import dev.kord.core.behavior.channel.asChannelOf +import dev.kord.core.entity.channel.TextChannel + +public class SelectorTestExtension : Extension() { + override val name: String = "Select Menus Test" + + override suspend fun setup() { + publicSlashCommand { + name = "selector" + description = "Test selectors." + + publicSubCommand { + name = "public" + description = "Test public selectors." + + action { + respond { + components { + publicStringSelectMenu { + option("hi", "1") + option("hi hi", "2") + maximumChoices = null + + action { + respond { content = selected.joinToString("\n") } + } + } + + publicUserSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.map { it.asUser().username }.joinToString("\n") + } + } + } + + publicRoleSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.map { it.asRole().name }.joinToString("\n") + } + } + } + + publicChannelSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.map { it.id.value }.joinToString("\n") + } + } + } + + publicChannelSelectMenu { + maximumChoices = null + channelType(ChannelType.GuildText) + + action { + respond { + content = selected.map { it.asChannelOf().name }.joinToString("\n") + } + } + } + } + } + } + } + + ephemeralSubCommand { + name = "ephemeral" + description = "Test ephemeral selectors." + + action { + respond { + components { + ephemeralStringSelectMenu { + option("hi", "1") + option("hi hi", "2") + maximumChoices = null + + action { + respond { content = selected.joinToString("\n") } + } + } + + ephemeralUserSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.map { it.asUser().username }.joinToString("\n") + } + } + } + + ephemeralRoleSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.map { it.asRole().name }.joinToString("\n") + } + } + } + + ephemeralChannelSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.map { it.id.value }.joinToString("\n") + } + } + } + + ephemeralChannelSelectMenu { + maximumChoices = null + channelType(ChannelType.GuildText) + + action { + respond { + content = selected.map { it.asChannelOf().name }.joinToString("\n") + } + } + } + } + } + } + } + } + } +} diff --git a/token-parser/build.gradle.kts b/token-parser/build.gradle.kts index 1b7045e305..42d5d490a2 100644 --- a/token-parser/build.gradle.kts +++ b/token-parser/build.gradle.kts @@ -12,7 +12,7 @@ metadata { dependencies { implementation(libs.kotlin.stdlib) - implementation(libs.logging) // Basic logging setup + implementation(libs.bundles.logging) // Basic logging setup detektPlugins(libs.detekt) detektPlugins(libs.detekt.libraries) diff --git a/token-parser/src/main/kotlin/com/kotlindiscord/kord/extensions/parser/StringParser.kt b/token-parser/src/main/kotlin/com/kotlindiscord/kord/extensions/parser/StringParser.kt index 97504447f1..eec2488104 100644 --- a/token-parser/src/main/kotlin/com/kotlindiscord/kord/extensions/parser/StringParser.kt +++ b/token-parser/src/main/kotlin/com/kotlindiscord/kord/extensions/parser/StringParser.kt @@ -8,7 +8,7 @@ package com.kotlindiscord.kord.extensions.parser import com.kotlindiscord.kord.extensions.parser.tokens.NamedArgumentToken import com.kotlindiscord.kord.extensions.parser.tokens.PositionalArgumentToken -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging /** * String parser, tokenizing the input as requested by the function calls. Intended for command argument parsing, but